@tenorlab/vue-dashboard 1.5.45 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -43
- package/README.md +151 -125
- package/dist/vue-dashboard.es.js +1 -1
- package/package.json +3 -3
package/LICENSE
CHANGED
|
@@ -1,43 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
redistribution of the Software's source code—whether in its original form
|
|
23
|
-
or as a modified derivative work—is STRICTLY PROHIBITED.
|
|
24
|
-
|
|
25
|
-
You may not include this Software, or any portion thereof, in any:
|
|
26
|
-
- Software Development Kit (SDK)
|
|
27
|
-
- UI Component Library or Framework
|
|
28
|
-
- Website/Dashboard Starter Template or Boilerplate
|
|
29
|
-
- "Dashboard Builder" or similar tool intended for use by other developers.
|
|
30
|
-
|
|
31
|
-
3. DERIVATIVE WORKS & MODIFICATIONS
|
|
32
|
-
You are permitted to modify and extend the Software for the sole purpose of
|
|
33
|
-
building a specific "End Product" application. Modifying the code does not
|
|
34
|
-
transfer ownership of the underlying logic, nor does it grant permission
|
|
35
|
-
to bypass the Commercial License requirement or the Redistribution Restrictions.
|
|
36
|
-
|
|
37
|
-
4. TERMINATION
|
|
38
|
-
Failure to comply with these terms automatically terminates your right to
|
|
39
|
-
use the Software. Tenorlab reserves all rights not expressly granted herein.
|
|
40
|
-
|
|
41
|
-
5. CONTACT & INQUIRIES
|
|
42
|
-
For licensing questions or professional inquiries, please visit:
|
|
43
|
-
https://www.tenorlab.com
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 tenorlab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,38 +1,37 @@
|
|
|
1
1
|
# @tenorlab/vue-dashboard
|
|
2
2
|
|
|
3
|
-
[](https://opensource.org/licenses/MIT)
|
|
4
|
+
[](https://www.tenorlab.com)
|
|
5
|
+
[](https://vuejs.org/)
|
|
6
6
|
|
|
7
|
-
Foundation components for creating user-configurable, high-performance dashboards in Vue.
|
|
7
|
+
Foundation components for creating user-configurable, high-performance dashboards in Vue.
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Relationship to Core
|
|
10
10
|
|
|
11
11
|
This package extends **@tenorlab/dashboard-core**. It provides the Vue implementation of the core logic, including specialized hooks, state management, and a suite of UI components.
|
|
12
12
|
|
|
13
13
|
> **Note**: This package re-exports all types and utilities from `@tenorlab/dashboard-core`. You do not need to install the core package separately.
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
## Demos
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
## Pro Template Demos
|
|
17
|
+
- [React Demo](https://react.tenorlab.com) (built with @tenorlab/react-dashboard)
|
|
18
|
+
- [Vue Demo](https://vue.tenorlab.com) (built with @tenorlab/vue-dashboard)
|
|
19
|
+
- [Nuxt Demo](https://nuxt.tenorlab.com) (built with @tenorlab/vue-dashboard)
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
## ✨ Features
|
|
22
23
|
|
|
23
|
-
- **Type-Safe:** Deep integration with TypeScript 5.8+ for full IDE support.
|
|
24
|
-
- **State Management:** Built-in `useDashboardStore` and `useDashboardUndoService`.
|
|
25
|
-
- **User Configurable:** Ready-to-use components for adding, removing, and dragging widgets.
|
|
26
|
-
- **Themeable:** Native support for CSS Variables and Tailwind CSS.
|
|
27
|
-
- **Vite Optimized:** Full ESM support and tree-shakeable.
|
|
24
|
+
- **Type-Safe:** Deep integration with TypeScript 5.8+ for full IDE support.
|
|
25
|
+
- **State Management:** Built-in `useDashboardStore` and `useDashboardUndoService`.
|
|
26
|
+
- **User Configurable:** Ready-to-use components for adding, removing, and dragging widgets.
|
|
27
|
+
- **Themeable:** Native support for CSS Variables and Tailwind CSS.
|
|
28
|
+
- **Vite Optimized:** Full ESM support and tree-shakeable.
|
|
28
29
|
|
|
29
30
|
## 🚀 Quick Start
|
|
30
31
|
|
|
31
32
|
### Installation
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
```
|
|
34
|
+
```bash
|
|
36
35
|
# with npm
|
|
37
36
|
npm i @tenorlab/vue-dashboard
|
|
38
37
|
|
|
@@ -56,42 +55,35 @@ import '@tenorlab/vue-dashboard/styles.css'
|
|
|
56
55
|
|
|
57
56
|
Widgets should be organized by their loading strategy.
|
|
58
57
|
|
|
59
|
-
- **Bundled Widgets**: Place in `src/bundled-widgets/` (loaded immediately).
|
|
60
|
-
- **Async Widgets**: Place in `src/async-widgets/` (lazy-loaded).
|
|
58
|
+
- **Bundled Widgets**: Place in `src/bundled-widgets/` (loaded immediately).
|
|
59
|
+
- **Async Widgets**: Place in `src/async-widgets/` (lazy-loaded).
|
|
60
|
+
|
|
61
|
+
*(NOTE: These directory names are suggestions; you can use different names, or put the widgets under src/components if you prefer)*
|
|
62
|
+
|
|
61
63
|
|
|
62
64
|
Each widget requires a sub-directory using the `widget-name-here` convention.
|
|
63
65
|
|
|
64
66
|
#### Example: `WidgetTotalOrders`
|
|
65
67
|
|
|
66
68
|
Directory name `widget-total-orders`, files:
|
|
67
|
-
- WidgetTotalOrders.vue
|
|
68
|
-
- meta.ts
|
|
69
|
-
- index.ts
|
|
69
|
+
- WidgetTotalOrders.vue
|
|
70
|
+
- meta.ts
|
|
71
|
+
- index.ts
|
|
70
72
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
+
File: src/bundled-widgets/widget-total-orders/WidgetTotalOrders.vue:
|
|
74
|
+
```vue
|
|
73
75
|
<script setup lang="ts">
|
|
74
|
-
import type {
|
|
75
|
-
IDashboardWidgetProps,
|
|
76
|
-
TDashboardWidgetKey,
|
|
77
|
-
TWidgetEmits,
|
|
78
|
-
} from '@tenorlab/vue-dashboard'
|
|
76
|
+
import type { IDashboardWidgetProps, TWidgetEmits } from '@tenorlab/vue-dashboard'
|
|
79
77
|
import { DashboardWidgetBase, WrapperColumnContent, useWidgetEmits } from '@tenorlab/vue-dashboard'
|
|
80
78
|
|
|
81
|
-
defineProps<IDashboardWidgetProps>()
|
|
79
|
+
const props = defineProps<IDashboardWidgetProps>()
|
|
82
80
|
const emits = defineEmits<TWidgetEmits>()
|
|
83
|
-
const
|
|
81
|
+
const emitHandlers = useWidgetEmits(emits)
|
|
84
82
|
</script>
|
|
85
83
|
|
|
86
84
|
<template>
|
|
87
|
-
<DashboardWidgetBase
|
|
88
|
-
|
|
89
|
-
widgetKey="WidgetTotalOrders"
|
|
90
|
-
title="Total Orders"
|
|
91
|
-
@removeClick="onRemoveClick"
|
|
92
|
-
@moveClick="onMoveClick"
|
|
93
|
-
>
|
|
94
|
-
<WrapperColumnContent>
|
|
85
|
+
<DashboardWidgetBase v-bind="props" v-on="emitHandlers">
|
|
86
|
+
<WrapperColumnContent><!-- WrapperColumnContent is optional, you are free to use a div or other html elements here -->
|
|
95
87
|
<div class="dashboard-number number-xl text-primary">1,250</div>
|
|
96
88
|
<div class="text-sm">Orders this month</div>
|
|
97
89
|
</WrapperColumnContent>
|
|
@@ -99,11 +91,11 @@ const { removeClick: onRemoveClick, moveClick: onMoveClick } = useWidgetEmits(em
|
|
|
99
91
|
</template>
|
|
100
92
|
```
|
|
101
93
|
|
|
94
|
+
File: src/bundled-widgets/widget-total-orders/meta.ts:
|
|
102
95
|
```typescript
|
|
103
|
-
// file: src/bundled-widgets/widget-total-orders/meta.ts
|
|
104
96
|
import type { TWidgetMetaInfo } from '@tenorlab/vue-dashboard'
|
|
105
|
-
import { MonitorIcon as ComponentIcon } from '@tenorlab/vue-dashboard'
|
|
106
97
|
import { markRaw } from 'vue'
|
|
98
|
+
import { ReceiptIcon as ComponentIcon } from 'lucide-vue-next'
|
|
107
99
|
|
|
108
100
|
// Define the metadata object for the plugin
|
|
109
101
|
export const WidgetTotalOrdersMeta: TWidgetMetaInfo = {
|
|
@@ -116,8 +108,8 @@ export const WidgetTotalOrdersMeta: TWidgetMetaInfo = {
|
|
|
116
108
|
}
|
|
117
109
|
```
|
|
118
110
|
|
|
111
|
+
File: src/bundled-widgets/widget-total-orders/index.ts:
|
|
119
112
|
```typescript
|
|
120
|
-
// file: src/bundled-widgets/widget-total-orders/index.ts
|
|
121
113
|
import WidgetTotalOrders from './WidgetTotalOrders.vue'
|
|
122
114
|
export default WidgetTotalOrders
|
|
123
115
|
```
|
|
@@ -128,25 +120,26 @@ export default WidgetTotalOrders
|
|
|
128
120
|
Create `src/widgets-catalog.ts` in your project root. This file manages how widgets are discovered (locally via Vite's `import.meta.glob` or remotely via CDN).
|
|
129
121
|
|
|
130
122
|
|
|
123
|
+
File: src/widgets-catalog.ts:
|
|
131
124
|
```typescript
|
|
132
|
-
|
|
125
|
+
import { WidgetContainerColumn, WidgetContainerLarge, WidgetContainerRow } from '@tenorlab/vue-dashboard'
|
|
126
|
+
import { createStaticEntry, localWidgetDiscovery, remoteWidgetDiscovery } from '@tenorlab/vue-dashboard/core'
|
|
127
|
+
import { getWidgetsManifestUrl } from '@/utils/'
|
|
128
|
+
|
|
129
|
+
// optional: other static widgets to add manually
|
|
133
130
|
import {
|
|
131
|
+
WidgetSmallCardSample,
|
|
132
|
+
} from './other-widgets/other-widgets'
|
|
133
|
+
// meta data map for other static widgets:
|
|
134
|
+
import { otherWidgetsMetaMap } from './other-widgets/other-widgets-meta'
|
|
135
|
+
import type {
|
|
134
136
|
IDynamicWidgetCatalogEntry,
|
|
135
137
|
TDashboardWidgetCatalog,
|
|
136
138
|
TWidgetMetaInfoBase,
|
|
137
|
-
WidgetContainerColumn,
|
|
138
|
-
WidgetContainerLarge,
|
|
139
|
-
WidgetContainerRow,
|
|
140
139
|
TWidgetFactory,
|
|
140
|
+
TDashboardWidgetKey,
|
|
141
|
+
TWidgetMetaInfo,
|
|
141
142
|
} from '@tenorlab/vue-dashboard'
|
|
142
|
-
import {
|
|
143
|
-
createStaticEntry,
|
|
144
|
-
localWidgetDiscovery,
|
|
145
|
-
remoteWidgetDiscovery,
|
|
146
|
-
} from '@tenorlab/vue-dashboard/core'
|
|
147
|
-
|
|
148
|
-
import WidgetRecentPaymentInfo from './other-widgets/WidgetRecentPaymentInfo.vue'
|
|
149
|
-
//import { getWidgetsManifestUrl } from '@/utils'
|
|
150
143
|
|
|
151
144
|
const bundledWidgetsSrcPath = '/src/bundled-widgets'
|
|
152
145
|
const asyncWidgetsSrcPath = '/src/async-widgets'
|
|
@@ -169,7 +162,7 @@ const allMetaModules = import.meta.glob('/src/**/widget-*/meta.ts', {
|
|
|
169
162
|
eager: true,
|
|
170
163
|
}) as Record<string, Record<string, TWidgetMetaInfoBase>>
|
|
171
164
|
|
|
172
|
-
const hasPermission = (
|
|
165
|
+
const hasPermission = (_user_: any, _permission: string) => true
|
|
173
166
|
|
|
174
167
|
/**
|
|
175
168
|
* @name getWidgetCatalog
|
|
@@ -178,34 +171,51 @@ const hasPermission = (_user: any, _permission: string) => true
|
|
|
178
171
|
export const getWidgetCatalog = async (user: any | null): Promise<TDashboardWidgetCatalog> => {
|
|
179
172
|
// A. Register Static Core Components
|
|
180
173
|
const catalogMapEntries: [string, IDynamicWidgetCatalogEntry][] = [
|
|
181
|
-
createStaticEntry('WidgetContainer', WidgetContainerColumn),
|
|
182
|
-
createStaticEntry('WidgetContainerRow', WidgetContainerRow),
|
|
183
|
-
createStaticEntry(
|
|
174
|
+
createStaticEntry('WidgetContainer', WidgetContainerColumn, otherWidgetsMetaMap['WidgetContainer']),
|
|
175
|
+
createStaticEntry('WidgetContainerRow', WidgetContainerRow, otherWidgetsMetaMap['WidgetContainerRow']),
|
|
176
|
+
createStaticEntry(
|
|
177
|
+
'WidgetContainerLarge',
|
|
178
|
+
WidgetContainerLarge,
|
|
179
|
+
otherWidgetsMetaMap['WidgetContainerLarge'],
|
|
180
|
+
),
|
|
184
181
|
]
|
|
185
182
|
|
|
186
|
-
// B. Register Business Static Widgets
|
|
187
|
-
//
|
|
183
|
+
// B. Optional: Register Business Static Widgets manually:
|
|
184
|
+
// Example: you could filter further by permissions and user type if needed
|
|
188
185
|
if (hasPermission(user, 'some-permission')) {
|
|
189
|
-
|
|
186
|
+
// i.e.:
|
|
187
|
+
// catalogMapEntries.push(
|
|
188
|
+
// createStaticEntry(
|
|
189
|
+
// 'WidgetThatRequiresPermissions',
|
|
190
|
+
// WidgetThatRequiresPermissions,
|
|
191
|
+
// otherWidgetsMetaMap['WidgetThatRequiresPermissions'],
|
|
192
|
+
// ),
|
|
193
|
+
// )
|
|
190
194
|
}
|
|
191
195
|
|
|
192
|
-
//
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
196
|
+
// C. Register widgets automatically with the localWidgetDiscovery helper:
|
|
197
|
+
// (bundled widgets are included always, non-lazy)
|
|
198
|
+
catalogMapEntries.push(
|
|
199
|
+
...localWidgetDiscovery(
|
|
200
|
+
bundledWidgetsSrcPath,
|
|
201
|
+
bundledWidgetModules,
|
|
202
|
+
allMetaModules,
|
|
203
|
+
false, // lazy: false
|
|
204
|
+
),
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
// D. Register "lazy" widgets automatically with the localWidgetDiscovery helper:
|
|
208
|
+
// (async widgets are not incuded, they are lazy loaded at run time)
|
|
209
|
+
catalogMapEntries.push(
|
|
210
|
+
...localWidgetDiscovery(
|
|
211
|
+
asyncWidgetsSrcPath,
|
|
212
|
+
asyncWidgetModules,
|
|
213
|
+
allMetaModules,
|
|
214
|
+
true, // lazy: true
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
// E. Optional: Remote discovery of -pre-built widgets hosted on a CDN (requires advance importMaps setup and other configuration)
|
|
209
219
|
/*const manifestUrl = getWidgetsManifestUrl()
|
|
210
220
|
if (manifestUrl.length > 0) {
|
|
211
221
|
const remoteResponse = await remoteWidgetDiscovery(manifestUrl)
|
|
@@ -222,22 +232,23 @@ export const getWidgetCatalog = async (user: any | null): Promise<TDashboardWidg
|
|
|
222
232
|
|
|
223
233
|
Use a `dashboard-defaults.ts` file to define initial layouts based on user roles.
|
|
224
234
|
|
|
235
|
+
File: src/dashboard-defaults.ts:
|
|
225
236
|
```typescript
|
|
226
|
-
|
|
227
|
-
import {
|
|
237
|
+
import { blankDashboardConfig, cssSettingsCatalog } from '@tenorlab/vue-dashboard/core'
|
|
238
|
+
import { getWidgetCatalog } from './widgets-catalog'
|
|
239
|
+
import type {
|
|
228
240
|
TDashboardWidgetKey,
|
|
229
241
|
IChildWidgetConfigEntry,
|
|
230
242
|
IDashboardConfig,
|
|
231
243
|
TDashboardWidgetCatalog,
|
|
232
244
|
} from '@tenorlab/vue-dashboard'
|
|
233
|
-
import { blankDashboardConfig, cssSettingsCatalog } from '@tenorlab/vue-dashboard/core'
|
|
234
|
-
import { getWidgetCatalog } from './widgets-catalog'
|
|
235
245
|
|
|
236
246
|
// reserved identifier to be used only for the default dashboard
|
|
237
247
|
const DEFAULT_DASHBOARD_ID = 'default' as const
|
|
238
248
|
const DEFAULT_DASHBOARD_NAME = 'Default' as const
|
|
239
249
|
|
|
240
|
-
|
|
250
|
+
// default dashboard config for Regular user type
|
|
251
|
+
const getDefaultDashboardForRegularUser = (
|
|
241
252
|
user: any,
|
|
242
253
|
clientAppKey: string,
|
|
243
254
|
availableWidgetKeys: TDashboardWidgetKey[]
|
|
@@ -249,10 +260,21 @@ const getDefaultDashboardForCustomerUser = (
|
|
|
249
260
|
dashboardId: DEFAULT_DASHBOARD_ID,
|
|
250
261
|
dashboardName: DEFAULT_DASHBOARD_NAME,
|
|
251
262
|
zoomScale: 1,
|
|
252
|
-
responsiveGrid:
|
|
253
|
-
widgets: [
|
|
263
|
+
responsiveGrid: true,
|
|
264
|
+
widgets: [
|
|
265
|
+
'WidgetContainer_container1', // will contain other widgets specified in the childWidgetsConfig secitno below
|
|
266
|
+
'WidgetBarGradients',
|
|
267
|
+
],
|
|
254
268
|
childWidgetsConfig: [
|
|
255
|
-
|
|
269
|
+
// two widgets go into container1:
|
|
270
|
+
{
|
|
271
|
+
parentWidgetKey: 'WidgetContainer_container1',
|
|
272
|
+
widgetKey: 'WidgetTotalOrders'
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
parentWidgetKey: 'WidgetContainer_container1',
|
|
276
|
+
widgetKey: 'WidgetTotalOrders'
|
|
277
|
+
}
|
|
256
278
|
],
|
|
257
279
|
cssSettings: [...cssSettingsCatalog]
|
|
258
280
|
}
|
|
@@ -267,10 +289,16 @@ export const getDashboardDefaults = async (
|
|
|
267
289
|
}> => {
|
|
268
290
|
const widgetsCatalog = await getWidgetCatalog(user)
|
|
269
291
|
|
|
270
|
-
if (!user)
|
|
292
|
+
if (!user) {
|
|
293
|
+
return {
|
|
294
|
+
dashboardConfig: blankDashboardConfig,
|
|
295
|
+
widgetsCatalog,
|
|
296
|
+
}
|
|
297
|
+
}
|
|
271
298
|
|
|
272
299
|
return {
|
|
273
|
-
|
|
300
|
+
// Optional, you could use different routines depending on user role:
|
|
301
|
+
dashboardConfig: getDefaultDashboardForRegularUser(user, clientAppKey, [...widgetsCatalog.keys()]),
|
|
274
302
|
widgetsCatalog
|
|
275
303
|
}
|
|
276
304
|
}
|
|
@@ -280,15 +308,11 @@ export const getDashboardDefaults = async (
|
|
|
280
308
|
|
|
281
309
|
Use this for a simplified, non-editable view of the dashboard.
|
|
282
310
|
|
|
283
|
-
|
|
284
|
-
|
|
311
|
+
File: src/views/DashboardReadonly.vue:
|
|
285
312
|
```vue
|
|
286
313
|
<script setup lang="ts">
|
|
287
|
-
|
|
288
|
-
import { reactive, watch, onMounted } from 'vue'
|
|
314
|
+
import { reactive, shallowRef, watch, onMounted } from 'vue'
|
|
289
315
|
import {
|
|
290
|
-
IDashboardConfig,
|
|
291
|
-
TDashboardWidgetCatalog,
|
|
292
316
|
useDashboardStore,
|
|
293
317
|
} from '@tenorlab/vue-dashboard'
|
|
294
318
|
import {
|
|
@@ -298,6 +322,10 @@ import {
|
|
|
298
322
|
} from '@tenorlab/vue-dashboard/core'
|
|
299
323
|
import { DynamicWidgetLoader, DashboardGrid } from '@tenorlab/vue-dashboard'
|
|
300
324
|
import { getDashboardDefaults } from '../dashboard-defaults'
|
|
325
|
+
import type {
|
|
326
|
+
IDashboardConfig,
|
|
327
|
+
TDashboardWidgetCatalog,
|
|
328
|
+
} from '@tenorlab/vue-dashboard'
|
|
301
329
|
|
|
302
330
|
const clientAppKey = 'myclientapp'
|
|
303
331
|
const user = { id: 1234 }
|
|
@@ -312,15 +340,13 @@ const {
|
|
|
312
340
|
targetContainerKey,
|
|
313
341
|
} = dashboardStore.computed
|
|
314
342
|
|
|
315
|
-
|
|
316
|
-
defaultDashboardConfig: IDashboardConfig
|
|
317
|
-
widgetsCatalog: TDashboardWidgetCatalog
|
|
318
|
-
}
|
|
319
|
-
|
|
343
|
+
// 1. Standard reactive state for primitive data
|
|
320
344
|
const localState = reactive<TState>({
|
|
321
345
|
defaultDashboardConfig: blankDashboardConfig,
|
|
322
|
-
widgetsCatalog: new Map(),
|
|
323
346
|
})
|
|
347
|
+
// 2. Shallow ref for the Catalog (Zero deep-proxying)
|
|
348
|
+
const widgetsCatalog = shallowRef<TDashboardWidgetCatalog>(new Map())
|
|
349
|
+
const getWidgetCatalog = () => widgetsCatalog.value
|
|
324
350
|
|
|
325
351
|
const getDefaultDashboardConfig = (): IDashboardConfig => {
|
|
326
352
|
return localState.defaultDashboardConfig
|
|
@@ -331,11 +357,11 @@ async function _fetchDashboardConfig() {
|
|
|
331
357
|
const savedConfigs = await dashboardStorageService.getSavedDashboards(
|
|
332
358
|
userId,
|
|
333
359
|
clientAppKey,
|
|
334
|
-
|
|
360
|
+
getWidgetCatalog(),
|
|
335
361
|
defaultConfig,
|
|
336
362
|
)
|
|
337
363
|
dashboardStore.setAllDashboardConfigs(savedConfigs)
|
|
338
|
-
// show default or first dashboard
|
|
364
|
+
// show default dashboard or first dashboard
|
|
339
365
|
const dashboardConfig =
|
|
340
366
|
savedConfigs.find((x) => x.dashboardId === 'default') || savedConfigs[0] || defaultConfig
|
|
341
367
|
dashboardStore.setCurrentDashboardConfig(dashboardConfig)
|
|
@@ -346,7 +372,7 @@ async function _fetchDashboardConfig() {
|
|
|
346
372
|
onMounted(async () => {
|
|
347
373
|
const defaults = await getDashboardDefaults(user, clientAppKey)
|
|
348
374
|
localState.defaultDashboardConfig = defaults.dashboardConfig
|
|
349
|
-
|
|
375
|
+
widgetsCatalog.value = defaults.widgetsCatalog
|
|
350
376
|
await _fetchDashboardConfig()
|
|
351
377
|
})
|
|
352
378
|
</script>
|
|
@@ -367,7 +393,7 @@ onMounted(async () => {
|
|
|
367
393
|
:index="index"
|
|
368
394
|
:maxIndex="currentDashboardConfig.widgets.length - 1"
|
|
369
395
|
:childWidgetsConfig="currentDashboardConfig.childWidgetsConfig"
|
|
370
|
-
:widgetCatalog="
|
|
396
|
+
:widgetCatalog="widgetsCatalog"
|
|
371
397
|
:isEditing="isEditing"
|
|
372
398
|
:extraProps="dashboardContext"
|
|
373
399
|
@removeClick="() => {}"
|
|
@@ -382,8 +408,7 @@ onMounted(async () => {
|
|
|
382
408
|
|
|
383
409
|
#### 5. Full Editable Dashboard
|
|
384
410
|
|
|
385
|
-
For
|
|
386
|
-
|
|
411
|
+
For editable dashboard examples, including **Undo/Redo**, **Zooming**, **Catalog Flyouts**, and **Multiple Dashboards**, please refer to the [Pro Template](https://www.tenorlab.com).
|
|
387
412
|
|
|
388
413
|
|
|
389
414
|
------
|
|
@@ -392,28 +417,35 @@ For a complete example including **Undo/Redo**, **Zooming**, **Catalog Flyouts**
|
|
|
392
417
|
|
|
393
418
|
### UI Components
|
|
394
419
|
|
|
395
|
-
- **`DashboardGrid`**: The main layout
|
|
396
|
-
- **`WidgetContainer`**:
|
|
397
|
-
- **`WidgetsCatalogFlyout`**: A slide-out panel for users to browse and add new widgets.
|
|
398
|
-
- **`DynamicWidgetLoader`**:
|
|
420
|
+
- **`DashboardGrid`**: The main dashboard layout that position widgets within a responsive grid.
|
|
421
|
+
- **`WidgetContainer`**: A special "widget" that is a container for other widgets.
|
|
422
|
+
- **`WidgetsCatalogFlyout`**: A slide-out panel for users to browse and add new widgets on editable dashboards.
|
|
423
|
+
- **`DynamicWidgetLoader`**: The core lazy-loading widget loader that renders the widgets within the grid.
|
|
399
424
|
|
|
400
425
|
### Hooks & State
|
|
401
426
|
|
|
402
427
|
- **`useDashboardStore`**: Access the underlying reactive store to manage widget state, layout, and configuration.
|
|
403
|
-
- **`useDashboardUndoService`**: Provides `undo` and `redo` functionality for user layout changes.
|
|
428
|
+
- **`useDashboardUndoService`**: Provides `undo` and `redo` functionality for user layout changes in editable dashboard (optional).
|
|
404
429
|
|
|
405
430
|
|
|
406
431
|
------
|
|
407
432
|
|
|
408
433
|
|
|
409
434
|
## Links
|
|
435
|
+
|
|
436
|
+
### Open source core packages
|
|
410
437
|
- [@tenorlab/react-dashboard](https://www.npmjs.com/package/@tenorlab/react-dashboard): React-specific components
|
|
411
438
|
- [@tenorlab/vue-dashboard](https://www.npmjs.com/package/@tenorlab/vue-dashboard): Vue-specific components
|
|
412
|
-
|
|
439
|
+
|
|
440
|
+
### Pro Template Demos
|
|
413
441
|
- [React Demo](https://react.tenorlab.com) (built with @tenorlab/react-dashboard)
|
|
414
442
|
- [Vue Demo](https://vue.tenorlab.com) (built with @tenorlab/vue-dashboard)
|
|
443
|
+
- [Nuxt Demo](https://nuxt.tenorlab.com) (built with @tenorlab/vue-dashboard)
|
|
444
|
+
|
|
445
|
+
### Others
|
|
415
446
|
- [Buy a License](https://payhip.com/b/gPBpo)
|
|
416
447
|
- [Follow on BlueSky](https://bsky.app/profile/tenorlab.bsky.social)
|
|
448
|
+
- [Official Website](https://www.tenorlab.com)
|
|
417
449
|
|
|
418
450
|
|
|
419
451
|
------
|
|
@@ -421,21 +453,15 @@ For a complete example including **Undo/Redo**, **Zooming**, **Catalog Flyouts**
|
|
|
421
453
|
|
|
422
454
|
## ⚖️ Licensing & Usage
|
|
423
455
|
|
|
424
|
-
|
|
456
|
+
**@tenorlab/vue-dashboard** is [MIT licensed](https://opensource.org/licenses/MIT).
|
|
425
457
|
|
|
426
|
-
|
|
427
|
-
If you are a student, hobbyist, or working on a non-profit open-source project, you may use this library for free under the terms of the **Polyform Non-Commercial 1.0.0** license.
|
|
458
|
+
It provides the foundational components and logic for building dashboards. You are free to use it in any project, personal or commercial.
|
|
428
459
|
|
|
429
|
-
|
|
430
|
-
A **Tenorlab Commercial License** is required if you are:
|
|
431
|
-
* Building a dashboard for company internal or external use, including all company websites and applications.
|
|
432
|
-
* Building a dashboard for a paying client.
|
|
433
|
-
* Using the library within a for-profit company or startup.
|
|
434
|
-
* Including the library in a product that is sold or requires a subscription.
|
|
460
|
+
## ⚡️ Go Pro and Save Time: Tenorlab App Template
|
|
435
461
|
|
|
436
|
-
|
|
462
|
+
A commercial license for a full-blown professional app template is available for purchase [**here**](https://www.tenorlab.com) and comes with:
|
|
437
463
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
*
|
|
441
|
-
*
|
|
464
|
+
* **Full Application Shell:** A clean, optimized Vite + TypeScript project structure (with either React, Vue or Nuxt).
|
|
465
|
+
* **Dashboard Management:** Production-ready logic for creating, listing, renaming, and deleting multiple user-defined dashboards.
|
|
466
|
+
* **Implementation Examples:** Best patterns for both "Read-Only" (Analyst view) and "User-Editable" (Admin view) dashboard modes, a dynamic dashboard menu, etc.
|
|
467
|
+
* **Tenorlab Theme Engine:** A sophisticated Tailwind-based system supporting multiple custom themes (not just Light/Dark mode).
|
package/dist/vue-dashboard.es.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { reactive as de, computed as h, readonly as we, defineComponent as y,
|
|
1
|
+
import { reactive as de, computed as h, readonly as we, defineComponent as y, openBlock as i, createElementBlock as g, normalizeClass as p, createElementVNode as o, createStaticVNode as G, withModifiers as H, normalizeStyle as ne, renderSlot as R, mergeProps as Z, createBlock as E, withCtx as B, unref as m, toDisplayString as M, ref as j, onMounted as ye, onUnmounted as be, createVNode as I, Fragment as U, renderList as P, shallowRef as re, watchEffect as _e, markRaw as J, defineAsyncComponent as ke, resolveComponent as $e, Suspense as Ie, resolveDynamicComponent as ce, createSlots as De, createTextVNode as ue, withKeys as ae, watch as Be, createCommentVNode as ee } from "vue";
|
|
2
2
|
import { blankDashboardConfig as oe, dashboardStoreUtils as q, getDistinctCssClasses as N, ensureZoomScaleIsWithinRange as Ke, parseContainerTitle as ge, dashboardSettingsUtils as We, getWidgetMetaFromCatalog as Me } from "./core.es.js";
|
|
3
3
|
const Ee = () => ({
|
|
4
4
|
isLoading: !1,
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tenorlab/vue-dashboard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "Foundation components for creating user-configurable dashboards in Vue",
|
|
5
5
|
"author": "Damiano Fusco",
|
|
6
|
-
"license": "
|
|
6
|
+
"license": "MIT",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"main": "./dist/vue-dashboard.es.js",
|
|
51
51
|
"module": "./dist/vue-dashboard.es.js",
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@tenorlab/dashboard-core": "^1.
|
|
53
|
+
"@tenorlab/dashboard-core": "^1.6.0",
|
|
54
54
|
"@types/node": "^24.10.1",
|
|
55
55
|
"@vitejs/plugin-vue": "^6.0.3",
|
|
56
56
|
"prettier": "^3.7.4",
|