Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9175799ec6 | |||
| eeb863b197 | |||
| e697419843 | |||
| a69f504c2f | |||
| 2d1d9d901b | |||
| 811ec492d8 | |||
| 761b0b678b | |||
| 12a7156928 | |||
| 59a870c3bc | |||
| 13fa654c0f | |||
| 3616bbb9a7 | |||
| 27c071f7dc | |||
| ac1ef4e497 | |||
| 9c61c0542b |
61
changelog.md
61
changelog.md
@@ -1,5 +1,66 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-12-19 - 3.4.0 - feat(dees-appui-base)
|
||||||
|
overhaul AppUI core: replace simple view rendering with a full-featured ViewRegistry (caching, hide/show lifecycle, async lazy-loading), introduce view lifecycle hooks and activation context, add activity log API/component, remove built-in router and state manager, and update configuration interfaces and demos
|
||||||
|
|
||||||
|
- Removed files: app.router.ts and state.manager.ts — routing and state-persistence internals were removed (breaking).
|
||||||
|
- ViewRegistry rewritten: supports cached instances, activate/deactivate lifecycle, canDeactivate checks, async content loading, parameterized routes, and legacy renderView kept as deprecated compatibility.
|
||||||
|
- New interfaces added/changed: IViewActivationContext, IViewLifecycle, IActivityEntry, IActivityLogAPI, IViewLifecycleEvent; IViewDefinition.content now accepts async loaders and a cache flag; IMainMenuConfig and ITab expanded (logo, groups, badges).
|
||||||
|
- Activity log: dees-appui-activitylog now implements IActivityLogAPI and exposes reactive entries; demo and readme updated with usage and examples.
|
||||||
|
- App config changed: routing and statePersistence config entries removed/adjusted; defaultView moved into IAppConfig; view change and lifecycle event shapes changed (breaking).
|
||||||
|
- Demos and documentation: dees-appui-base demo and readme added/updated to showcase new lifecycle hooks, secondary menu behavior, activity log and new APIs.
|
||||||
|
|
||||||
|
## 2025-12-19 - 3.3.3 - fix(tests)
|
||||||
|
update test imports to new dees-input-wysiwyg paths
|
||||||
|
|
||||||
|
- Updated imports in test/test.wysiwyg-registry.both.ts to point to ts_web/elements/00group-input/dees-input-wysiwyg/*
|
||||||
|
- Aligns test references with relocated WYSIWYG block handlers and block registration module; no behavior changes to implementation
|
||||||
|
|
||||||
|
## 2025-12-19 - 3.3.2 - fix(build)
|
||||||
|
update build config, bump dependencies, and adjust test import paths after element reorganization
|
||||||
|
|
||||||
|
- npmextra.json: renamed gitzone entry to @git.zone/cli, moved tsdoc key to @git.zone/tsdoc, added @ship.zone/szci entry and added release registries + accessLevel
|
||||||
|
- package.json: bumped @design.estate/dees-wcctools ^2.0.1 -> ^3.1.0, lucide ^0.560.0 -> ^0.562.0, @git.zone/tsbuild ^3.1.2 -> ^4.0.2, @types/node ^25.0.0 -> ^25.0.3
|
||||||
|
- tests: updated import paths to follow reorganized source layout (wysiwyg files moved under elements/00group-input/dees-input-wysiwyg and dees-contextmenu moved to elements/dees-contextmenu/dees-contextmenu.js); updated BlockRegistry and blockregistration import paths
|
||||||
|
- Purpose: align tests and build metadata with refactored element file locations and updated tool/dependency versions
|
||||||
|
|
||||||
|
## 2025-12-11 - 3.3.1 - fix(dees-pdf-viewer)
|
||||||
|
Scroll active PDF thumbnail into view after rendering and on page changes; update dependency versions
|
||||||
|
|
||||||
|
- Ensure the active thumbnail is scrolled into view after thumbnails are rendered (improves sidebar navigation for dees-pdf-viewer).
|
||||||
|
- Scroll the thumbnail into view when navigating pages if the sidebar is visible (prevents the active page from being off-screen).
|
||||||
|
- Retain re-setup of the intersection observer for lazy-loading pages after thumbnail re-render.
|
||||||
|
- Bumped dependencies in package.json: @design.estate/dees-wcctools -> ^2.0.1, lucide -> ^0.560.0, @git.zone/tswatch -> ^2.3.13, @types/node -> ^25.0.0.
|
||||||
|
|
||||||
|
## 2025-12-09 - 3.3.0 - feat(dees-appui-base)
|
||||||
|
Add unified App UI API to dees-appui-base with ViewRegistry, AppRouter and StateManager
|
||||||
|
|
||||||
|
- Introduce ViewRegistry for declarative view registration and rendering (supports tag names, element classes and template functions).
|
||||||
|
- Add AppRouter with hash/history/external/none modes, URL synchronization, navigate/back/forward and onRouteChange listener support.
|
||||||
|
- Add StateManager to persist UI state (localStorage, sessionStorage or in-memory) with save/load/update/clear APIs.
|
||||||
|
- Extend interfaces (interfaces/appconfig.ts) with IAppConfig, IViewDefinition, IRoutingConfig, IStatePersistenceConfig and IAppUIState.
|
||||||
|
- Expose new public DeesAppuiBase methods: configure, navigateToView, getCurrentView, getUIState, restoreUIState, saveState, loadState, getViewRegistry, getRouter.
|
||||||
|
- Maintain backward compatibility with existing property-based API and slot usage.
|
||||||
|
- Export new modules (view.registry, app.router, state.manager) from dees-appui-base index and update element exports.
|
||||||
|
|
||||||
|
## 2025-12-08 - 3.2.0 - feat(dees-simple-appdash,dees-simple-login,dees-terminal)
|
||||||
|
Revamp UI: dashboard & login styling, standardize icons to Lucide, and add terminal background/config
|
||||||
|
|
||||||
|
- Standardize icon usage to Lucide prefixes in dees-simple-appdash; add fallback handling for legacy icon names
|
||||||
|
- Revamped dees-simple-appdash sidebar: updated spacing, typography, header icon wrapper, scrollbar styling, section labels, hover/selected states, and visual indicators
|
||||||
|
- Change 'Logout' label to 'Sign out' in dees-simple-appdash and add explicit status classes for controlbar (connected, terminal)
|
||||||
|
- Improve terminal UX: smoother launch/close animations, updated shadow and sizing logic in dees-simple-appdash
|
||||||
|
- Add background property to dees-terminal, sync it to a CSS variable and apply it to xterm theme for configurable terminal background
|
||||||
|
- Redesign dees-simple-login: new header/subheader, card layout, spacing, and updated submit text to 'Sign in'
|
||||||
|
- Bump devDependency @git.zone/tswatch to ^2.3.5
|
||||||
|
|
||||||
|
## 2025-12-08 - 3.1.2 - fix(DeesAppuiMainmenu, DeesAppuiSecondarymenu)
|
||||||
|
Add position: relative to main and secondary app UI menus to fix positioning of overlays and tooltips
|
||||||
|
|
||||||
|
- ts_web/elements/00group-appui/dees-appui-mainmenu/dees-appui-mainmenu.ts: add `position: relative` to host styles
|
||||||
|
- ts_web/elements/00group-appui/dees-appui-secondarymenu/dees-appui-secondarymenu.ts: add `position: relative` to host styles
|
||||||
|
- Fixes incorrect positioning for absolutely positioned children (tooltips, overlays, badges) inside the main and secondary menus
|
||||||
|
|
||||||
## 2025-12-08 - 3.1.1 - fix(dees-appui)
|
## 2025-12-08 - 3.1.1 - fix(dees-appui)
|
||||||
Extract demos for main and secondary app menus, adjust collapsed styles and toggle placement, bump devDependency
|
Extract demos for main and secondary app menus, adjust collapsed styles and toggle placement, bump devDependency
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"gitzone": {
|
"@git.zone/cli": {
|
||||||
"projectType": "wcc",
|
"projectType": "wcc",
|
||||||
"module": {
|
"module": {
|
||||||
"githost": "code.foss.global",
|
"githost": "code.foss.global",
|
||||||
@@ -35,13 +35,19 @@
|
|||||||
"Modern Web",
|
"Modern Web",
|
||||||
"Frontend Development"
|
"Frontend Development"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"registries": [
|
||||||
|
"https://verdaccio.lossless.digital",
|
||||||
|
"https://registry.npmjs.org"
|
||||||
|
],
|
||||||
|
"accessLevel": "public"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"npmci": {
|
"@git.zone/tsdoc": {
|
||||||
"npmGlobalTools": [],
|
|
||||||
"npmAccessLevel": "public"
|
|
||||||
},
|
|
||||||
"tsdoc": {
|
|
||||||
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
||||||
|
},
|
||||||
|
"@ship.zone/szci": {
|
||||||
|
"npmGlobalTools": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
12
package.json
12
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@design.estate/dees-catalog",
|
"name": "@design.estate/dees-catalog",
|
||||||
"version": "3.1.1",
|
"version": "3.4.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.",
|
"description": "A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.",
|
||||||
"main": "dist_ts_web/index.js",
|
"main": "dist_ts_web/index.js",
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@design.estate/dees-domtools": "^2.3.6",
|
"@design.estate/dees-domtools": "^2.3.6",
|
||||||
"@design.estate/dees-element": "^2.1.3",
|
"@design.estate/dees-element": "^2.1.3",
|
||||||
"@design.estate/dees-wcctools": "^1.2.1",
|
"@design.estate/dees-wcctools": "^3.1.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "^7.1.0",
|
"@fortawesome/fontawesome-svg-core": "^7.1.0",
|
||||||
"@fortawesome/free-brands-svg-icons": "^7.1.0",
|
"@fortawesome/free-brands-svg-icons": "^7.1.0",
|
||||||
"@fortawesome/free-regular-svg-icons": "^7.1.0",
|
"@fortawesome/free-regular-svg-icons": "^7.1.0",
|
||||||
@@ -38,20 +38,20 @@
|
|||||||
"highlight.js": "11.11.1",
|
"highlight.js": "11.11.1",
|
||||||
"ibantools": "^4.5.1",
|
"ibantools": "^4.5.1",
|
||||||
"lit": "^3.3.1",
|
"lit": "^3.3.1",
|
||||||
"lucide": "^0.555.0",
|
"lucide": "^0.562.0",
|
||||||
"monaco-editor": "0.52.2",
|
"monaco-editor": "0.52.2",
|
||||||
"pdfjs-dist": "^4.10.38",
|
"pdfjs-dist": "^4.10.38",
|
||||||
"xterm": "^5.3.0",
|
"xterm": "^5.3.0",
|
||||||
"xterm-addon-fit": "^0.8.0"
|
"xterm-addon-fit": "^0.8.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^3.1.2",
|
"@git.zone/tsbuild": "^4.0.2",
|
||||||
"@git.zone/tsbundle": "^2.6.3",
|
"@git.zone/tsbundle": "^2.6.3",
|
||||||
"@git.zone/tstest": "^3.1.3",
|
"@git.zone/tstest": "^3.1.3",
|
||||||
"@git.zone/tswatch": "^2.3.2",
|
"@git.zone/tswatch": "^2.3.13",
|
||||||
"@push.rocks/projectinfo": "^5.0.2",
|
"@push.rocks/projectinfo": "^5.0.2",
|
||||||
"@push.rocks/tapbundle": "^6.0.3",
|
"@push.rocks/tapbundle": "^6.0.3",
|
||||||
"@types/node": "^24.10.1"
|
"@types/node": "^25.0.3"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"ts/**/*",
|
"ts/**/*",
|
||||||
|
|||||||
268
pnpm-lock.yaml
generated
268
pnpm-lock.yaml
generated
@@ -15,8 +15,8 @@ importers:
|
|||||||
specifier: ^2.1.3
|
specifier: ^2.1.3
|
||||||
version: 2.1.3
|
version: 2.1.3
|
||||||
'@design.estate/dees-wcctools':
|
'@design.estate/dees-wcctools':
|
||||||
specifier: ^1.2.1
|
specifier: ^3.1.0
|
||||||
version: 1.2.1
|
version: 3.1.0
|
||||||
'@fortawesome/fontawesome-svg-core':
|
'@fortawesome/fontawesome-svg-core':
|
||||||
specifier: ^7.1.0
|
specifier: ^7.1.0
|
||||||
version: 7.1.0
|
version: 7.1.0
|
||||||
@@ -75,8 +75,8 @@ importers:
|
|||||||
specifier: ^3.3.1
|
specifier: ^3.3.1
|
||||||
version: 3.3.1
|
version: 3.3.1
|
||||||
lucide:
|
lucide:
|
||||||
specifier: ^0.555.0
|
specifier: ^0.562.0
|
||||||
version: 0.555.0
|
version: 0.562.0
|
||||||
monaco-editor:
|
monaco-editor:
|
||||||
specifier: 0.52.2
|
specifier: 0.52.2
|
||||||
version: 0.52.2
|
version: 0.52.2
|
||||||
@@ -91,17 +91,17 @@ importers:
|
|||||||
version: 0.8.0(xterm@5.3.0)
|
version: 0.8.0(xterm@5.3.0)
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@git.zone/tsbuild':
|
'@git.zone/tsbuild':
|
||||||
specifier: ^3.1.2
|
specifier: ^4.0.2
|
||||||
version: 3.1.2
|
version: 4.0.2
|
||||||
'@git.zone/tsbundle':
|
'@git.zone/tsbundle':
|
||||||
specifier: ^2.6.3
|
specifier: ^2.6.3
|
||||||
version: 2.6.3
|
version: 2.6.3
|
||||||
'@git.zone/tstest':
|
'@git.zone/tstest':
|
||||||
specifier: ^3.1.3
|
specifier: ^3.1.3
|
||||||
version: 3.1.3(@push.rocks/smartserve@1.3.0)(socks@2.8.7)(typescript@5.9.3)
|
version: 3.1.3(@push.rocks/smartserve@1.4.0)(socks@2.8.7)(typescript@5.9.3)
|
||||||
'@git.zone/tswatch':
|
'@git.zone/tswatch':
|
||||||
specifier: ^2.3.2
|
specifier: ^2.3.13
|
||||||
version: 2.3.2(@tiptap/pm@2.27.1)
|
version: 2.3.13(@tiptap/pm@2.27.1)
|
||||||
'@push.rocks/projectinfo':
|
'@push.rocks/projectinfo':
|
||||||
specifier: ^5.0.2
|
specifier: ^5.0.2
|
||||||
version: 5.0.2
|
version: 5.0.2
|
||||||
@@ -109,8 +109,8 @@ importers:
|
|||||||
specifier: ^6.0.3
|
specifier: ^6.0.3
|
||||||
version: 6.0.3(socks@2.8.7)
|
version: 6.0.3(socks@2.8.7)
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^24.10.1
|
specifier: ^25.0.3
|
||||||
version: 24.10.1
|
version: 25.0.3
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
@@ -126,8 +126,8 @@ packages:
|
|||||||
'@api.global/typedserver@3.0.80':
|
'@api.global/typedserver@3.0.80':
|
||||||
resolution: {integrity: sha512-dcp0oXsjBL+XdFg1wUUP08uJQid5bQ0Yv3V3Y3lnI2QCbat0FU+Tsb0TZRnZ4+P150Vj/ITBqJUgDzFsF34grA==}
|
resolution: {integrity: sha512-dcp0oXsjBL+XdFg1wUUP08uJQid5bQ0Yv3V3Y3lnI2QCbat0FU+Tsb0TZRnZ4+P150Vj/ITBqJUgDzFsF34grA==}
|
||||||
|
|
||||||
'@api.global/typedserver@7.11.0':
|
'@api.global/typedserver@7.11.1':
|
||||||
resolution: {integrity: sha512-DHB3oGRgiLbAeRRFAlXIbjvNACw9YqsfOtLVCPq0nUcGMGSsUQ4SNrJ5OGk9GhVF8bl/jn8SF8w08rnApmZ0uw==}
|
resolution: {integrity: sha512-1vQUJ2/DszDFHVkCmqqBy/qNiIP/jltFN3KxHtoNLxjbdBZYfw1Zd1Odjs6YSPalAD0p8wQ/alJblJEAewNQVg==}
|
||||||
|
|
||||||
'@api.global/typedsocket@3.1.1':
|
'@api.global/typedsocket@3.1.1':
|
||||||
resolution: {integrity: sha512-Wkz3NlhmfdZMKqXXI2c2dMtGGmSmhdOegZiziL+9b2mqPYdc7Gd8AZRdEOKvbSoIvc9G22/5BEadIWHrfq66TA==}
|
resolution: {integrity: sha512-Wkz3NlhmfdZMKqXXI2c2dMtGGmSmhdOegZiziL+9b2mqPYdc7Gd8AZRdEOKvbSoIvc9G22/5BEadIWHrfq66TA==}
|
||||||
@@ -316,9 +316,15 @@ packages:
|
|||||||
'@borewit/text-codec@0.1.1':
|
'@borewit/text-codec@0.1.1':
|
||||||
resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==}
|
resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==}
|
||||||
|
|
||||||
|
'@cfworker/json-schema@4.1.1':
|
||||||
|
resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==}
|
||||||
|
|
||||||
'@cloudflare/workers-types@4.20251205.0':
|
'@cloudflare/workers-types@4.20251205.0':
|
||||||
resolution: {integrity: sha512-7pup7fYkuQW5XD8RUS/vkxF9SXlrGyCXuZ4ro3uVQvca/GTeSa+8bZ8T4wbq1Aea5lmLIGSlKbhl2msME7bRBA==}
|
resolution: {integrity: sha512-7pup7fYkuQW5XD8RUS/vkxF9SXlrGyCXuZ4ro3uVQvca/GTeSa+8bZ8T4wbq1Aea5lmLIGSlKbhl2msME7bRBA==}
|
||||||
|
|
||||||
|
'@cloudflare/workers-types@4.20251211.0':
|
||||||
|
resolution: {integrity: sha512-e87o6KbelCz7dnI5ngrGT2ca15vJZ+COb2eqJ52iDHmOaujyC6aYZ71e2vor8X6V9T6tcDElC5sAqPR93j09EA==}
|
||||||
|
|
||||||
'@colors/colors@1.6.0':
|
'@colors/colors@1.6.0':
|
||||||
resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==}
|
resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==}
|
||||||
engines: {node: '>=0.1.90'}
|
engines: {node: '>=0.1.90'}
|
||||||
@@ -329,8 +335,8 @@ packages:
|
|||||||
'@dabh/diagnostics@2.0.8':
|
'@dabh/diagnostics@2.0.8':
|
||||||
resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==}
|
resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==}
|
||||||
|
|
||||||
'@design.estate/dees-catalog@2.0.7':
|
'@design.estate/dees-catalog@3.3.0':
|
||||||
resolution: {integrity: sha512-rshv71LqA2PXaEEf6C1/hv6Yu2ovRuWaZhdnUznCDpjdYgxBq7PHkiHCNvg/m6wJ9Ue/03HcuuPqtj2bksgAag==}
|
resolution: {integrity: sha512-iPT8QtMrheH8ZO6xmyZfAC+51smTsA9Og63BC2mKFixoGiAG7UScN1ywM/uHEmnKCd+KBY+dn4ZaHyR4UrrX2A==}
|
||||||
|
|
||||||
'@design.estate/dees-comms@1.0.30':
|
'@design.estate/dees-comms@1.0.30':
|
||||||
resolution: {integrity: sha512-KchMlklJfKAjQiJiR0xmofXtQ27VgZtBIxcMwPE9d+h3jJRv+lPZxzBQVOM0eyM0uS44S5vJMZ11IeV4uDXSHg==}
|
resolution: {integrity: sha512-KchMlklJfKAjQiJiR0xmofXtQ27VgZtBIxcMwPE9d+h3jJRv+lPZxzBQVOM0eyM0uS44S5vJMZ11IeV4uDXSHg==}
|
||||||
@@ -341,8 +347,11 @@ packages:
|
|||||||
'@design.estate/dees-element@2.1.3':
|
'@design.estate/dees-element@2.1.3':
|
||||||
resolution: {integrity: sha512-TjXWxVcdSPaT1IOk31ckfxvAZnJLuTxhFGsNCKoh63/UE2FVf6slp8//UFvN+ADigiA9ZsY0azkY99XbJCwDDA==}
|
resolution: {integrity: sha512-TjXWxVcdSPaT1IOk31ckfxvAZnJLuTxhFGsNCKoh63/UE2FVf6slp8//UFvN+ADigiA9ZsY0azkY99XbJCwDDA==}
|
||||||
|
|
||||||
'@design.estate/dees-wcctools@1.2.1':
|
'@design.estate/dees-wcctools@1.3.0':
|
||||||
resolution: {integrity: sha512-ESFas1MPPwDUcXRssyHRsc63XPTBJSTBA+5RhYXDZx8mbV6HxEKiJR8Oz1Mv7DBdW+ZSuUTD/fA6Aa/fCxGYTQ==}
|
resolution: {integrity: sha512-+yd8c1gTIKNRQYCvG0xu6Am8dHsRm7ymluX2gnoBQN4aFOpZgIBi/v9CvGyPhTD1p/VRouIBz1wsUCejnwrFCA==}
|
||||||
|
|
||||||
|
'@design.estate/dees-wcctools@3.1.0':
|
||||||
|
resolution: {integrity: sha512-EZsgvZZ736PORt8FkHvzKzsmsHDkYOngcWfL1eBEutT3HBQq4PW5X6PVWSHXFzbhI6rt3O07GJHEIa+xU2mSkQ==}
|
||||||
|
|
||||||
'@emnapi/core@1.7.1':
|
'@emnapi/core@1.7.1':
|
||||||
resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==}
|
resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==}
|
||||||
@@ -532,16 +541,16 @@ packages:
|
|||||||
resolution: {integrity: sha512-Udu3K7SzAo9N013qt7qmm22/wo2hADdheXtBfxFTecp+ogsc0caQNRKEb7pkvvagUGOpG9wJC1ViH6WXs8oXIA==}
|
resolution: {integrity: sha512-Udu3K7SzAo9N013qt7qmm22/wo2hADdheXtBfxFTecp+ogsc0caQNRKEb7pkvvagUGOpG9wJC1ViH6WXs8oXIA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
'@git.zone/tsbuild@3.1.2':
|
'@git.zone/tsbuild@4.0.2':
|
||||||
resolution: {integrity: sha512-K0u840Qo0WEhvcpAtktvdBX6KEXjelU32o820WzcK7dMA7dd2YV+mPOEYfbmWLcdtFJkrjkigQq5fpLhTN4oKQ==}
|
resolution: {integrity: sha512-LcRlFnDbcUe53Pdoob585iXq9TAT90TyEaYl/wml/etFoPeBX+oQLm6GryejUPXrUP7i1opyTonadkQN1OyXOA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@git.zone/tsbundle@2.6.3':
|
'@git.zone/tsbundle@2.6.3':
|
||||||
resolution: {integrity: sha512-YD1qMYA/4eOuF57V0ccR+xo6ww1+QOYFA2K5gBPFBDNh9VdfvWxxDhOUybja8lT9PVMoli8PHG5WA5tKJkdXIQ==}
|
resolution: {integrity: sha512-YD1qMYA/4eOuF57V0ccR+xo6ww1+QOYFA2K5gBPFBDNh9VdfvWxxDhOUybja8lT9PVMoli8PHG5WA5tKJkdXIQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@git.zone/tspublish@1.10.3':
|
'@git.zone/tspublish@1.11.0':
|
||||||
resolution: {integrity: sha512-o2/jvNsdLC8SRdH1kQ7JjNOQNu9el0FpJ/QOW3mgiC5C9reuTp18iU4kijsVVLgvw4KZv6Z289SoKPh3HPsS0g==}
|
resolution: {integrity: sha512-dkgaDBTzZJ53lAV72r7OW/W7l/KqpkncFuPojr11JO35OKAbjjDhZbAwPv4oGX9NplyXrhC5VJRPNX/orqNTHA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@git.zone/tsrun@2.0.0':
|
'@git.zone/tsrun@2.0.0':
|
||||||
@@ -552,8 +561,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-t+/cKV21JHK8X7NGAmihs5M/eMm+V+jn4R5rzfwGG97WJFAcP5qE1Os9VYtyZw3tx/NZXA2yA4abo/ELluTuRA==}
|
resolution: {integrity: sha512-t+/cKV21JHK8X7NGAmihs5M/eMm+V+jn4R5rzfwGG97WJFAcP5qE1Os9VYtyZw3tx/NZXA2yA4abo/ELluTuRA==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@git.zone/tswatch@2.3.2':
|
'@git.zone/tswatch@2.3.13':
|
||||||
resolution: {integrity: sha512-M4B4D7BlfGb3ssgMHFMHc+hhEmxY6WzNd87ihuLReXMD+twjMA99u2hyAvAnboa3lERlixvHz/nN+rdbGH5VlA==}
|
resolution: {integrity: sha512-43995OlWl8UzCA+cX3ehYba/ksm6CqHbMljHKjosrDRpx8EU+LY4bWTc8JT/Ldgwsw3iW9vur2bBqpgMmdeJJw==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@hapi/bourne@3.0.0':
|
'@hapi/bourne@3.0.0':
|
||||||
@@ -806,6 +815,9 @@ packages:
|
|||||||
'@push.rocks/mongodump@1.1.0':
|
'@push.rocks/mongodump@1.1.0':
|
||||||
resolution: {integrity: sha512-kW0ZUGyf1e4nwloVwBQjNId+MzgTcNS834C+RxH21i1NqyOubbpWZtJtPP+K+s35nSJRyCTy3ICfBMdDBTAm2w==}
|
resolution: {integrity: sha512-kW0ZUGyf1e4nwloVwBQjNId+MzgTcNS834C+RxH21i1NqyOubbpWZtJtPP+K+s35nSJRyCTy3ICfBMdDBTAm2w==}
|
||||||
|
|
||||||
|
'@push.rocks/npmextra@5.3.3':
|
||||||
|
resolution: {integrity: sha512-snLpSHwaQ5OXlZzF1KX/FY71W5LwajjBzor82Vue0smjEPnSeUPY5/JcVdMwtdprdJe13pc/EQQuIiL/zw4/yg==}
|
||||||
|
|
||||||
'@push.rocks/projectinfo@5.0.2':
|
'@push.rocks/projectinfo@5.0.2':
|
||||||
resolution: {integrity: sha512-zzieCal6jwR++o+fDl8gMpWkNV2cGEsbT96vCNZu/H9kr0iqRmapOiA4DFadkhOnhlDqvRr6TPaXESu2YUbI8Q==}
|
resolution: {integrity: sha512-zzieCal6jwR++o+fDl8gMpWkNV2cGEsbT96vCNZu/H9kr0iqRmapOiA4DFadkhOnhlDqvRr6TPaXESu2YUbI8Q==}
|
||||||
|
|
||||||
@@ -864,6 +876,9 @@ packages:
|
|||||||
'@push.rocks/smartexit@1.0.23':
|
'@push.rocks/smartexit@1.0.23':
|
||||||
resolution: {integrity: sha512-WmwKYcwbHBByoABhHHB+PAjr5475AtD/xBh1mDcqPrFsOOUOZq3BBUdpq25wI3ccu/SZB5IwaimiVzadls6HkA==}
|
resolution: {integrity: sha512-WmwKYcwbHBByoABhHHB+PAjr5475AtD/xBh1mDcqPrFsOOUOZq3BBUdpq25wI3ccu/SZB5IwaimiVzadls6HkA==}
|
||||||
|
|
||||||
|
'@push.rocks/smartexit@1.1.0':
|
||||||
|
resolution: {integrity: sha512-GD8VLIbxQuwvhPXwK4eH162XAYSj+M3wGKWGNO3i1iY4bj8P3BARcgsWx6/ntN3aCo5ygWtrevrfD5iecYY2Ng==}
|
||||||
|
|
||||||
'@push.rocks/smartexpect@2.5.0':
|
'@push.rocks/smartexpect@2.5.0':
|
||||||
resolution: {integrity: sha512-yoyuCoQ3tTiAriuvF+/09fNbVfFnacudL2SwHSzPhX/ugaE7VTSWXQ9A34eKOWvil0MPyDcOY36fVZDxvrPd8A==}
|
resolution: {integrity: sha512-yoyuCoQ3tTiAriuvF+/09fNbVfFnacudL2SwHSzPhX/ugaE7VTSWXQ9A34eKOWvil0MPyDcOY36fVZDxvrPd8A==}
|
||||||
|
|
||||||
@@ -882,9 +897,15 @@ packages:
|
|||||||
'@push.rocks/smartfile@13.1.0':
|
'@push.rocks/smartfile@13.1.0':
|
||||||
resolution: {integrity: sha512-bSjH9vHl6l1nbe/gcSi4PcutFcTHUCVkMuQGGTVtn1cOgCuOXIHV04uhOXrZoKvlcSxxoiq8THolFt65lqn7cg==}
|
resolution: {integrity: sha512-bSjH9vHl6l1nbe/gcSi4PcutFcTHUCVkMuQGGTVtn1cOgCuOXIHV04uhOXrZoKvlcSxxoiq8THolFt65lqn7cg==}
|
||||||
|
|
||||||
|
'@push.rocks/smartfile@13.1.2':
|
||||||
|
resolution: {integrity: sha512-DaEhwmnGEpX4coeeToaw4cZe3pNBhH7CY1iGr+d3pIXihozREvzzAR9/0i2r7bUXXL5+Lgy8YYIk5ZS+fwxMKA==}
|
||||||
|
|
||||||
'@push.rocks/smartfs@1.2.0':
|
'@push.rocks/smartfs@1.2.0':
|
||||||
resolution: {integrity: sha512-1R47jJZwX869z7DYgKeAZKTU1SbGnM7W/ZmgsI7AkQQhiascNqY3/gF4V5kIprmuf1WhpRbCbZyum8s7J1LDdg==}
|
resolution: {integrity: sha512-1R47jJZwX869z7DYgKeAZKTU1SbGnM7W/ZmgsI7AkQQhiascNqY3/gF4V5kIprmuf1WhpRbCbZyum8s7J1LDdg==}
|
||||||
|
|
||||||
|
'@push.rocks/smartfs@1.3.1':
|
||||||
|
resolution: {integrity: sha512-ZSduVS8tM+/erbyCTvRRvc9gLWwbpqN5xdIIkMr+gub7fowSeJb7tR2rnGwySa63DyimU0q2KTp79VV9YqGLeg==}
|
||||||
|
|
||||||
'@push.rocks/smartguard@3.1.0':
|
'@push.rocks/smartguard@3.1.0':
|
||||||
resolution: {integrity: sha512-J23q84f1O+TwFGmd4lrO9XLHUh2DaLXo9PN/9VmTWYzTkQDv5JehmifXVI0esophXcCIfbdIu6hbt7/aHlDF4A==}
|
resolution: {integrity: sha512-J23q84f1O+TwFGmd4lrO9XLHUh2DaLXo9PN/9VmTWYzTkQDv5JehmifXVI0esophXcCIfbdIu6hbt7/aHlDF4A==}
|
||||||
|
|
||||||
@@ -981,8 +1002,8 @@ packages:
|
|||||||
'@push.rocks/smarts3@3.0.3':
|
'@push.rocks/smarts3@3.0.3':
|
||||||
resolution: {integrity: sha512-Y9nXMwurthJ9Z7yi0RwjhPFUC58aY8Mhia8kFo6Xj1tBM4LE8Oxg/ydejF7otHqQGr3QyqV5C4YrDEG17rUuzg==}
|
resolution: {integrity: sha512-Y9nXMwurthJ9Z7yi0RwjhPFUC58aY8Mhia8kFo6Xj1tBM4LE8Oxg/ydejF7otHqQGr3QyqV5C4YrDEG17rUuzg==}
|
||||||
|
|
||||||
'@push.rocks/smartserve@1.3.0':
|
'@push.rocks/smartserve@1.4.0':
|
||||||
resolution: {integrity: sha512-4ZR9uKVWXVAPzU5wtCQ1mA9jNmOlUl3oGr50EceLT6803UwbNcst7Ek/BhzSaZ0qb2pz0jO5T/V+icgvZ1/5ww==}
|
resolution: {integrity: sha512-cEoXZQSBX3pOv9AyhxRPkrMAWzs2XQhTBmW95BFtTSNzZdji0XgqUu92p7iuF+NVuTFX1QZ8+dbCClLCoRRW7g==}
|
||||||
|
|
||||||
'@push.rocks/smartshell@3.3.0':
|
'@push.rocks/smartshell@3.3.0':
|
||||||
resolution: {integrity: sha512-m0w618H6YBs+vXGz1CgS4nPi5CUAnqRtckcS9/koGwfcIx1IpjqmiP47BoCTbdgcv0IPUxQVBG1IXTHPuZ8Z5g==}
|
resolution: {integrity: sha512-m0w618H6YBs+vXGz1CgS4nPi5CUAnqRtckcS9/koGwfcIx1IpjqmiP47BoCTbdgcv0IPUxQVBG1IXTHPuZ8Z5g==}
|
||||||
@@ -1020,8 +1041,8 @@ packages:
|
|||||||
'@push.rocks/smartversion@3.0.5':
|
'@push.rocks/smartversion@3.0.5':
|
||||||
resolution: {integrity: sha512-8MZSo1yqyaKxKq0Q5N188l4un++9GFWVbhCAX5mXJwewZHn97ujffTeL+eOQYpWFTEpUhaq1QhL4NhqObBCt1Q==}
|
resolution: {integrity: sha512-8MZSo1yqyaKxKq0Q5N188l4un++9GFWVbhCAX5mXJwewZHn97ujffTeL+eOQYpWFTEpUhaq1QhL4NhqObBCt1Q==}
|
||||||
|
|
||||||
'@push.rocks/smartwatch@5.1.0':
|
'@push.rocks/smartwatch@6.3.0':
|
||||||
resolution: {integrity: sha512-vf4yT8F8V+vstnrr0sh64rGF/8Z8dZlLR4T95KcVtm7F0H3dKxLcvZ3HT0xayw0gQow8+CCtDdts9Wgq5ipJTw==}
|
resolution: {integrity: sha512-TeZ1PGBoBMpC4/CK8StIj5InEiFfKp7xWJSm3aYMjB/uaoeRP0vXqv1ORIC/TKYGJuEDuAXUsit8tZVjn0qT1Q==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
'@push.rocks/smartxml@2.0.0':
|
'@push.rocks/smartxml@2.0.0':
|
||||||
@@ -1795,8 +1816,8 @@ packages:
|
|||||||
'@types/node-forge@1.3.14':
|
'@types/node-forge@1.3.14':
|
||||||
resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==}
|
resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==}
|
||||||
|
|
||||||
'@types/node@24.10.1':
|
'@types/node@25.0.3':
|
||||||
resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==}
|
resolution: {integrity: sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==}
|
||||||
|
|
||||||
'@types/parse5@6.0.3':
|
'@types/parse5@6.0.3':
|
||||||
resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
|
resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==}
|
||||||
@@ -3306,6 +3327,9 @@ packages:
|
|||||||
lucide@0.555.0:
|
lucide@0.555.0:
|
||||||
resolution: {integrity: sha512-R7BkO2/XRpMADcMIRn1UOZOvirxr2Z6s/R82k0EUK71ZXXrlRbvkVwTAIf+9DRApeyH+zNMIGfiUdmrOhoAygQ==}
|
resolution: {integrity: sha512-R7BkO2/XRpMADcMIRn1UOZOvirxr2Z6s/R82k0EUK71ZXXrlRbvkVwTAIf+9DRApeyH+zNMIGfiUdmrOhoAygQ==}
|
||||||
|
|
||||||
|
lucide@0.562.0:
|
||||||
|
resolution: {integrity: sha512-k1Fb8ZMnRQovWRlea7Jr0b9UKA29IM7/cu79+mJrhVohvA2YC/Ti3Sk+G+h/SIu3IlrKT4RAbWMHUBBQd1O6XA==}
|
||||||
|
|
||||||
make-dir@3.1.0:
|
make-dir@3.1.0:
|
||||||
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
|
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -4656,11 +4680,11 @@ snapshots:
|
|||||||
'@push.rocks/webrequest': 3.0.37
|
'@push.rocks/webrequest': 3.0.37
|
||||||
'@push.rocks/webstream': 1.0.10
|
'@push.rocks/webstream': 1.0.10
|
||||||
|
|
||||||
'@api.global/typedserver@3.0.80(@push.rocks/smartserve@1.3.0)':
|
'@api.global/typedserver@3.0.80(@push.rocks/smartserve@1.4.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@api.global/typedrequest': 3.2.5
|
'@api.global/typedrequest': 3.2.5
|
||||||
'@api.global/typedrequest-interfaces': 3.0.19
|
'@api.global/typedrequest-interfaces': 3.0.19
|
||||||
'@api.global/typedsocket': 3.1.1(@push.rocks/smartserve@1.3.0)
|
'@api.global/typedsocket': 3.1.1(@push.rocks/smartserve@1.4.0)
|
||||||
'@cloudflare/workers-types': 4.20251205.0
|
'@cloudflare/workers-types': 4.20251205.0
|
||||||
'@design.estate/dees-comms': 1.0.30
|
'@design.estate/dees-comms': 1.0.30
|
||||||
'@push.rocks/lik': 6.2.2
|
'@push.rocks/lik': 6.2.2
|
||||||
@@ -4704,13 +4728,13 @@ snapshots:
|
|||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
- vue
|
- vue
|
||||||
|
|
||||||
'@api.global/typedserver@7.11.0(@tiptap/pm@2.27.1)':
|
'@api.global/typedserver@7.11.1(@tiptap/pm@2.27.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@api.global/typedrequest': 3.2.5
|
'@api.global/typedrequest': 3.2.5
|
||||||
'@api.global/typedrequest-interfaces': 3.0.19
|
'@api.global/typedrequest-interfaces': 3.0.19
|
||||||
'@api.global/typedsocket': 4.1.0(@push.rocks/smartserve@1.3.0)
|
'@api.global/typedsocket': 4.1.0(@push.rocks/smartserve@1.4.0)
|
||||||
'@cloudflare/workers-types': 4.20251205.0
|
'@cloudflare/workers-types': 4.20251211.0
|
||||||
'@design.estate/dees-catalog': 2.0.7(@tiptap/pm@2.27.1)
|
'@design.estate/dees-catalog': 3.3.0(@tiptap/pm@2.27.1)
|
||||||
'@design.estate/dees-comms': 1.0.30
|
'@design.estate/dees-comms': 1.0.30
|
||||||
'@push.rocks/lik': 6.2.2
|
'@push.rocks/lik': 6.2.2
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
@@ -4731,11 +4755,11 @@ snapshots:
|
|||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrequest': 5.0.1
|
'@push.rocks/smartrequest': 5.0.1
|
||||||
'@push.rocks/smartrx': 3.0.10
|
'@push.rocks/smartrx': 3.0.10
|
||||||
'@push.rocks/smartserve': 1.3.0
|
'@push.rocks/smartserve': 1.4.0
|
||||||
'@push.rocks/smartsitemap': 2.0.4
|
'@push.rocks/smartsitemap': 2.0.4
|
||||||
'@push.rocks/smartstream': 3.2.5
|
'@push.rocks/smartstream': 3.2.5
|
||||||
'@push.rocks/smarttime': 4.1.1
|
'@push.rocks/smarttime': 4.1.1
|
||||||
'@push.rocks/smartwatch': 5.1.0
|
'@push.rocks/smartwatch': 6.3.0
|
||||||
'@push.rocks/taskbuffer': 3.5.0
|
'@push.rocks/taskbuffer': 3.5.0
|
||||||
'@push.rocks/webrequest': 4.0.1
|
'@push.rocks/webrequest': 4.0.1
|
||||||
'@push.rocks/webstore': 2.0.20
|
'@push.rocks/webstore': 2.0.20
|
||||||
@@ -4750,7 +4774,7 @@ snapshots:
|
|||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
- vue
|
- vue
|
||||||
|
|
||||||
'@api.global/typedsocket@3.1.1(@push.rocks/smartserve@1.3.0)':
|
'@api.global/typedsocket@3.1.1(@push.rocks/smartserve@1.4.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@api.global/typedrequest': 3.2.5
|
'@api.global/typedrequest': 3.2.5
|
||||||
'@api.global/typedrequest-interfaces': 3.0.19
|
'@api.global/typedrequest-interfaces': 3.0.19
|
||||||
@@ -4761,7 +4785,7 @@ snapshots:
|
|||||||
'@push.rocks/smartstring': 4.1.0
|
'@push.rocks/smartstring': 4.1.0
|
||||||
'@push.rocks/smarturl': 3.1.0
|
'@push.rocks/smarturl': 3.1.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@push.rocks/smartserve': 1.3.0
|
'@push.rocks/smartserve': 1.4.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@nuxt/kit'
|
- '@nuxt/kit'
|
||||||
- bufferutil
|
- bufferutil
|
||||||
@@ -4770,7 +4794,7 @@ snapshots:
|
|||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
- vue
|
- vue
|
||||||
|
|
||||||
'@api.global/typedsocket@4.1.0(@push.rocks/smartserve@1.3.0)':
|
'@api.global/typedsocket@4.1.0(@push.rocks/smartserve@1.4.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@api.global/typedrequest': 3.2.5
|
'@api.global/typedrequest': 3.2.5
|
||||||
'@api.global/typedrequest-interfaces': 3.0.19
|
'@api.global/typedrequest-interfaces': 3.0.19
|
||||||
@@ -4779,7 +4803,7 @@ snapshots:
|
|||||||
'@push.rocks/smartjson': 5.2.0
|
'@push.rocks/smartjson': 5.2.0
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrx': 3.0.10
|
'@push.rocks/smartrx': 3.0.10
|
||||||
'@push.rocks/smartserve': 1.3.0
|
'@push.rocks/smartserve': 1.4.0
|
||||||
'@push.rocks/smartstring': 4.1.0
|
'@push.rocks/smartstring': 4.1.0
|
||||||
'@push.rocks/smarturl': 3.1.0
|
'@push.rocks/smarturl': 3.1.0
|
||||||
|
|
||||||
@@ -5274,8 +5298,12 @@ snapshots:
|
|||||||
|
|
||||||
'@borewit/text-codec@0.1.1': {}
|
'@borewit/text-codec@0.1.1': {}
|
||||||
|
|
||||||
|
'@cfworker/json-schema@4.1.1': {}
|
||||||
|
|
||||||
'@cloudflare/workers-types@4.20251205.0': {}
|
'@cloudflare/workers-types@4.20251205.0': {}
|
||||||
|
|
||||||
|
'@cloudflare/workers-types@4.20251211.0': {}
|
||||||
|
|
||||||
'@colors/colors@1.6.0': {}
|
'@colors/colors@1.6.0': {}
|
||||||
|
|
||||||
'@configvault.io/interfaces@1.0.17':
|
'@configvault.io/interfaces@1.0.17':
|
||||||
@@ -5288,11 +5316,11 @@ snapshots:
|
|||||||
enabled: 2.0.0
|
enabled: 2.0.0
|
||||||
kuler: 2.0.0
|
kuler: 2.0.0
|
||||||
|
|
||||||
'@design.estate/dees-catalog@2.0.7(@tiptap/pm@2.27.1)':
|
'@design.estate/dees-catalog@3.3.0(@tiptap/pm@2.27.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@design.estate/dees-domtools': 2.3.6
|
'@design.estate/dees-domtools': 2.3.6
|
||||||
'@design.estate/dees-element': 2.1.3
|
'@design.estate/dees-element': 2.1.3
|
||||||
'@design.estate/dees-wcctools': 1.2.1
|
'@design.estate/dees-wcctools': 1.3.0
|
||||||
'@fortawesome/fontawesome-svg-core': 7.1.0
|
'@fortawesome/fontawesome-svg-core': 7.1.0
|
||||||
'@fortawesome/free-brands-svg-icons': 7.1.0
|
'@fortawesome/free-brands-svg-icons': 7.1.0
|
||||||
'@fortawesome/free-regular-svg-icons': 7.1.0
|
'@fortawesome/free-regular-svg-icons': 7.1.0
|
||||||
@@ -5369,7 +5397,19 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- vue
|
- vue
|
||||||
|
|
||||||
'@design.estate/dees-wcctools@1.2.1':
|
'@design.estate/dees-wcctools@1.3.0':
|
||||||
|
dependencies:
|
||||||
|
'@design.estate/dees-domtools': 2.3.6
|
||||||
|
'@design.estate/dees-element': 2.1.3
|
||||||
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
|
lit: 3.3.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@nuxt/kit'
|
||||||
|
- react
|
||||||
|
- supports-color
|
||||||
|
- vue
|
||||||
|
|
||||||
|
'@design.estate/dees-wcctools@3.1.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@design.estate/dees-domtools': 2.3.6
|
'@design.estate/dees-domtools': 2.3.6
|
||||||
'@design.estate/dees-element': 2.1.3
|
'@design.estate/dees-element': 2.1.3
|
||||||
@@ -5497,13 +5537,14 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@fortawesome/fontawesome-common-types': 7.1.0
|
'@fortawesome/fontawesome-common-types': 7.1.0
|
||||||
|
|
||||||
'@git.zone/tsbuild@3.1.2':
|
'@git.zone/tsbuild@4.0.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@git.zone/tspublish': 1.10.3
|
'@git.zone/tspublish': 1.11.0
|
||||||
'@push.rocks/early': 4.0.4
|
'@push.rocks/early': 4.0.4
|
||||||
'@push.rocks/smartcli': 4.0.19
|
'@push.rocks/smartcli': 4.0.19
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartfile': 11.2.7
|
'@push.rocks/smartfile': 13.1.2
|
||||||
|
'@push.rocks/smartfs': 1.3.1
|
||||||
'@push.rocks/smartlog': 3.1.10
|
'@push.rocks/smartlog': 3.1.10
|
||||||
'@push.rocks/smartpath': 6.0.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
@@ -5538,16 +5579,18 @@ snapshots:
|
|||||||
- '@swc/helpers'
|
- '@swc/helpers'
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@git.zone/tspublish@1.10.3':
|
'@git.zone/tspublish@1.11.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/consolecolor': 2.0.3
|
'@push.rocks/consolecolor': 2.0.3
|
||||||
|
'@push.rocks/npmextra': 5.3.3
|
||||||
'@push.rocks/smartcli': 4.0.19
|
'@push.rocks/smartcli': 4.0.19
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartfile': 11.2.7
|
'@push.rocks/smartfile': 13.1.2
|
||||||
|
'@push.rocks/smartfs': 1.3.1
|
||||||
'@push.rocks/smartlog': 3.1.10
|
'@push.rocks/smartlog': 3.1.10
|
||||||
'@push.rocks/smartnpm': 2.0.6
|
'@push.rocks/smartnpm': 2.0.6
|
||||||
'@push.rocks/smartpath': 6.0.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
'@push.rocks/smartrequest': 4.4.2
|
'@push.rocks/smartrequest': 5.0.1
|
||||||
'@push.rocks/smartshell': 3.3.0
|
'@push.rocks/smartshell': 3.3.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@nuxt/kit'
|
- '@nuxt/kit'
|
||||||
@@ -5564,9 +5607,9 @@ snapshots:
|
|||||||
'@push.rocks/smartshell': 3.3.0
|
'@push.rocks/smartshell': 3.3.0
|
||||||
tsx: 4.21.0
|
tsx: 4.21.0
|
||||||
|
|
||||||
'@git.zone/tstest@3.1.3(@push.rocks/smartserve@1.3.0)(socks@2.8.7)(typescript@5.9.3)':
|
'@git.zone/tstest@3.1.3(@push.rocks/smartserve@1.4.0)(socks@2.8.7)(typescript@5.9.3)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@api.global/typedserver': 3.0.80(@push.rocks/smartserve@1.3.0)
|
'@api.global/typedserver': 3.0.80(@push.rocks/smartserve@1.4.0)
|
||||||
'@git.zone/tsbundle': 2.6.3
|
'@git.zone/tsbundle': 2.6.3
|
||||||
'@git.zone/tsrun': 2.0.0
|
'@git.zone/tsrun': 2.0.0
|
||||||
'@push.rocks/consolecolor': 2.0.3
|
'@push.rocks/consolecolor': 2.0.3
|
||||||
@@ -5613,9 +5656,9 @@ snapshots:
|
|||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
- vue
|
- vue
|
||||||
|
|
||||||
'@git.zone/tswatch@2.3.2(@tiptap/pm@2.27.1)':
|
'@git.zone/tswatch@2.3.13(@tiptap/pm@2.27.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@api.global/typedserver': 7.11.0(@tiptap/pm@2.27.1)
|
'@api.global/typedserver': 7.11.1(@tiptap/pm@2.27.1)
|
||||||
'@git.zone/tsbundle': 2.6.3
|
'@git.zone/tsbundle': 2.6.3
|
||||||
'@git.zone/tsrun': 2.0.0
|
'@git.zone/tsrun': 2.0.0
|
||||||
'@push.rocks/early': 4.0.4
|
'@push.rocks/early': 4.0.4
|
||||||
@@ -5626,7 +5669,7 @@ snapshots:
|
|||||||
'@push.rocks/smartlog': 3.1.10
|
'@push.rocks/smartlog': 3.1.10
|
||||||
'@push.rocks/smartlog-destination-local': 9.0.2
|
'@push.rocks/smartlog-destination-local': 9.0.2
|
||||||
'@push.rocks/smartshell': 3.3.0
|
'@push.rocks/smartshell': 3.3.0
|
||||||
'@push.rocks/smartwatch': 5.1.0
|
'@push.rocks/smartwatch': 6.3.0
|
||||||
'@push.rocks/taskbuffer': 3.5.0
|
'@push.rocks/taskbuffer': 3.5.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@nuxt/kit'
|
- '@nuxt/kit'
|
||||||
@@ -5672,7 +5715,7 @@ snapshots:
|
|||||||
'@jest/schemas': 29.6.3
|
'@jest/schemas': 29.6.3
|
||||||
'@types/istanbul-lib-coverage': 2.0.6
|
'@types/istanbul-lib-coverage': 2.0.6
|
||||||
'@types/istanbul-reports': 3.0.4
|
'@types/istanbul-reports': 3.0.4
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
'@types/yargs': 17.0.35
|
'@types/yargs': 17.0.35
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
|
|
||||||
@@ -5982,7 +6025,7 @@ snapshots:
|
|||||||
'@push.rocks/smartbucket': 3.3.10
|
'@push.rocks/smartbucket': 3.3.10
|
||||||
'@push.rocks/smartcache': 1.0.18
|
'@push.rocks/smartcache': 1.0.18
|
||||||
'@push.rocks/smartenv': 5.0.13
|
'@push.rocks/smartenv': 5.0.13
|
||||||
'@push.rocks/smartexit': 1.0.23
|
'@push.rocks/smartexit': 1.1.0
|
||||||
'@push.rocks/smartfile': 11.2.7
|
'@push.rocks/smartfile': 11.2.7
|
||||||
'@push.rocks/smartjson': 5.2.0
|
'@push.rocks/smartjson': 5.2.0
|
||||||
'@push.rocks/smartpath': 6.0.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
@@ -6027,6 +6070,23 @@ snapshots:
|
|||||||
- snappy
|
- snappy
|
||||||
- socks
|
- socks
|
||||||
|
|
||||||
|
'@push.rocks/npmextra@5.3.3':
|
||||||
|
dependencies:
|
||||||
|
'@push.rocks/qenv': 6.1.3
|
||||||
|
'@push.rocks/smartfile': 11.2.7
|
||||||
|
'@push.rocks/smartjson': 5.2.0
|
||||||
|
'@push.rocks/smartlog': 3.1.10
|
||||||
|
'@push.rocks/smartpath': 6.0.0
|
||||||
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
|
'@push.rocks/smartrx': 3.0.10
|
||||||
|
'@push.rocks/taskbuffer': 3.5.0
|
||||||
|
'@tsclass/tsclass': 9.3.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@nuxt/kit'
|
||||||
|
- react
|
||||||
|
- supports-color
|
||||||
|
- vue
|
||||||
|
|
||||||
'@push.rocks/projectinfo@5.0.2':
|
'@push.rocks/projectinfo@5.0.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartfile': 10.0.41
|
'@push.rocks/smartfile': 10.0.41
|
||||||
@@ -6045,7 +6105,7 @@ snapshots:
|
|||||||
'@push.rocks/smartarchive@4.2.4':
|
'@push.rocks/smartarchive@4.2.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartfile': 13.1.0
|
'@push.rocks/smartfile': 13.1.2
|
||||||
'@push.rocks/smartpath': 6.0.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrequest': 4.4.2
|
'@push.rocks/smartrequest': 4.4.2
|
||||||
@@ -6219,6 +6279,13 @@ snapshots:
|
|||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
tree-kill: 1.2.2
|
tree-kill: 1.2.2
|
||||||
|
|
||||||
|
'@push.rocks/smartexit@1.1.0':
|
||||||
|
dependencies:
|
||||||
|
'@push.rocks/lik': 6.2.2
|
||||||
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
|
tree-kill: 1.2.2
|
||||||
|
|
||||||
'@push.rocks/smartexpect@2.5.0':
|
'@push.rocks/smartexpect@2.5.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
@@ -6286,10 +6353,31 @@ snapshots:
|
|||||||
glob: 11.1.0
|
glob: 11.1.0
|
||||||
js-yaml: 4.1.1
|
js-yaml: 4.1.1
|
||||||
|
|
||||||
|
'@push.rocks/smartfile@13.1.2':
|
||||||
|
dependencies:
|
||||||
|
'@push.rocks/lik': 6.2.2
|
||||||
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
|
'@push.rocks/smartfile-interfaces': 1.0.7
|
||||||
|
'@push.rocks/smartfs': 1.3.1
|
||||||
|
'@push.rocks/smarthash': 3.2.6
|
||||||
|
'@push.rocks/smartjson': 5.2.0
|
||||||
|
'@push.rocks/smartmime': 2.0.4
|
||||||
|
'@push.rocks/smartpath': 6.0.0
|
||||||
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
|
'@push.rocks/smartrequest': 4.4.2
|
||||||
|
'@push.rocks/smartstream': 3.2.5
|
||||||
|
'@types/js-yaml': 4.0.9
|
||||||
|
glob: 11.1.0
|
||||||
|
js-yaml: 4.1.1
|
||||||
|
|
||||||
'@push.rocks/smartfs@1.2.0':
|
'@push.rocks/smartfs@1.2.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartpath': 6.0.0
|
'@push.rocks/smartpath': 6.0.0
|
||||||
|
|
||||||
|
'@push.rocks/smartfs@1.3.1':
|
||||||
|
dependencies:
|
||||||
|
'@push.rocks/smartpath': 6.0.0
|
||||||
|
|
||||||
'@push.rocks/smartguard@3.1.0':
|
'@push.rocks/smartguard@3.1.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
@@ -6555,9 +6643,10 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- aws-crt
|
- aws-crt
|
||||||
|
|
||||||
'@push.rocks/smartserve@1.3.0':
|
'@push.rocks/smartserve@1.4.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@api.global/typedrequest': 3.2.5
|
'@api.global/typedrequest': 3.2.5
|
||||||
|
'@cfworker/json-schema': 4.1.1
|
||||||
'@push.rocks/lik': 6.2.2
|
'@push.rocks/lik': 6.2.2
|
||||||
'@push.rocks/smartenv': 6.0.0
|
'@push.rocks/smartenv': 6.0.0
|
||||||
'@push.rocks/smartlog': 3.1.10
|
'@push.rocks/smartlog': 3.1.10
|
||||||
@@ -6588,7 +6677,7 @@ snapshots:
|
|||||||
'@push.rocks/smartsocket@2.1.0':
|
'@push.rocks/smartsocket@2.1.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@api.global/typedrequest-interfaces': 3.0.19
|
'@api.global/typedrequest-interfaces': 3.0.19
|
||||||
'@api.global/typedserver': 3.0.80(@push.rocks/smartserve@1.3.0)
|
'@api.global/typedserver': 3.0.80(@push.rocks/smartserve@1.4.0)
|
||||||
'@push.rocks/isohash': 2.0.1
|
'@push.rocks/isohash': 2.0.1
|
||||||
'@push.rocks/isounique': 1.0.5
|
'@push.rocks/isounique': 1.0.5
|
||||||
'@push.rocks/lik': 6.2.2
|
'@push.rocks/lik': 6.2.2
|
||||||
@@ -6672,12 +6761,13 @@ snapshots:
|
|||||||
'@types/semver': 7.7.1
|
'@types/semver': 7.7.1
|
||||||
semver: 7.7.3
|
semver: 7.7.3
|
||||||
|
|
||||||
'@push.rocks/smartwatch@5.1.0':
|
'@push.rocks/smartwatch@6.3.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/lik': 6.2.2
|
'@push.rocks/lik': 6.2.2
|
||||||
'@push.rocks/smartenv': 6.0.0
|
'@push.rocks/smartenv': 6.0.0
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrx': 3.0.10
|
'@push.rocks/smartrx': 3.0.10
|
||||||
|
chokidar: 5.0.0
|
||||||
picomatch: 4.0.3
|
picomatch: 4.0.3
|
||||||
|
|
||||||
'@push.rocks/smartxml@2.0.0':
|
'@push.rocks/smartxml@2.0.0':
|
||||||
@@ -7476,18 +7566,18 @@ snapshots:
|
|||||||
|
|
||||||
'@types/accepts@1.3.7':
|
'@types/accepts@1.3.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
|
|
||||||
'@types/babel__code-frame@7.0.6': {}
|
'@types/babel__code-frame@7.0.6': {}
|
||||||
|
|
||||||
'@types/bn.js@5.2.0':
|
'@types/bn.js@5.2.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
|
|
||||||
'@types/body-parser@1.19.6':
|
'@types/body-parser@1.19.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/connect': 3.4.38
|
'@types/connect': 3.4.38
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
|
|
||||||
'@types/buffer-json@2.0.3': {}
|
'@types/buffer-json@2.0.3': {}
|
||||||
|
|
||||||
@@ -7504,17 +7594,17 @@ snapshots:
|
|||||||
|
|
||||||
'@types/clean-css@4.2.11':
|
'@types/clean-css@4.2.11':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
source-map: 0.6.1
|
source-map: 0.6.1
|
||||||
|
|
||||||
'@types/co-body@6.1.3':
|
'@types/co-body@6.1.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
'@types/qs': 6.14.0
|
'@types/qs': 6.14.0
|
||||||
|
|
||||||
'@types/connect@3.4.38':
|
'@types/connect@3.4.38':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
|
|
||||||
'@types/content-disposition@0.5.9': {}
|
'@types/content-disposition@0.5.9': {}
|
||||||
|
|
||||||
@@ -7525,11 +7615,11 @@ snapshots:
|
|||||||
'@types/connect': 3.4.38
|
'@types/connect': 3.4.38
|
||||||
'@types/express': 5.0.6
|
'@types/express': 5.0.6
|
||||||
'@types/keygrip': 1.0.6
|
'@types/keygrip': 1.0.6
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
|
|
||||||
'@types/cors@2.8.19':
|
'@types/cors@2.8.19':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
|
|
||||||
'@types/debounce@1.2.4': {}
|
'@types/debounce@1.2.4': {}
|
||||||
|
|
||||||
@@ -7541,7 +7631,7 @@ snapshots:
|
|||||||
|
|
||||||
'@types/dns-packet@5.6.5':
|
'@types/dns-packet@5.6.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
|
|
||||||
'@types/elliptic@6.4.18':
|
'@types/elliptic@6.4.18':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -7549,7 +7639,7 @@ snapshots:
|
|||||||
|
|
||||||
'@types/express-serve-static-core@5.1.0':
|
'@types/express-serve-static-core@5.1.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
'@types/qs': 6.14.0
|
'@types/qs': 6.14.0
|
||||||
'@types/range-parser': 1.2.7
|
'@types/range-parser': 1.2.7
|
||||||
'@types/send': 1.2.1
|
'@types/send': 1.2.1
|
||||||
@@ -7562,17 +7652,17 @@ snapshots:
|
|||||||
|
|
||||||
'@types/from2@2.3.6':
|
'@types/from2@2.3.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
|
|
||||||
'@types/fs-extra@11.0.4':
|
'@types/fs-extra@11.0.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/jsonfile': 6.1.4
|
'@types/jsonfile': 6.1.4
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
|
|
||||||
'@types/glob@8.1.0':
|
'@types/glob@8.1.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/minimatch': 5.1.2
|
'@types/minimatch': 5.1.2
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
|
|
||||||
'@types/hast@3.0.4':
|
'@types/hast@3.0.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -7606,7 +7696,7 @@ snapshots:
|
|||||||
|
|
||||||
'@types/jsonfile@6.1.4':
|
'@types/jsonfile@6.1.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
|
|
||||||
'@types/keygrip@1.0.6': {}
|
'@types/keygrip@1.0.6': {}
|
||||||
|
|
||||||
@@ -7623,7 +7713,7 @@ snapshots:
|
|||||||
'@types/http-errors': 2.0.5
|
'@types/http-errors': 2.0.5
|
||||||
'@types/keygrip': 1.0.6
|
'@types/keygrip': 1.0.6
|
||||||
'@types/koa-compose': 3.2.9
|
'@types/koa-compose': 3.2.9
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
|
|
||||||
'@types/linkify-it@5.0.0': {}
|
'@types/linkify-it@5.0.0': {}
|
||||||
|
|
||||||
@@ -7646,9 +7736,9 @@ snapshots:
|
|||||||
|
|
||||||
'@types/node-forge@1.3.14':
|
'@types/node-forge@1.3.14':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
|
|
||||||
'@types/node@24.10.1':
|
'@types/node@25.0.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 7.16.0
|
undici-types: 7.16.0
|
||||||
|
|
||||||
@@ -7666,18 +7756,18 @@ snapshots:
|
|||||||
|
|
||||||
'@types/s3rver@3.7.4':
|
'@types/s3rver@3.7.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
|
|
||||||
'@types/semver@7.7.1': {}
|
'@types/semver@7.7.1': {}
|
||||||
|
|
||||||
'@types/send@1.2.1':
|
'@types/send@1.2.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
|
|
||||||
'@types/serve-static@2.2.0':
|
'@types/serve-static@2.2.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/http-errors': 2.0.5
|
'@types/http-errors': 2.0.5
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
|
|
||||||
'@types/sinon-chai@3.2.12':
|
'@types/sinon-chai@3.2.12':
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -7696,11 +7786,11 @@ snapshots:
|
|||||||
|
|
||||||
'@types/tar-stream@3.1.4':
|
'@types/tar-stream@3.1.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
|
|
||||||
'@types/through2@2.0.41':
|
'@types/through2@2.0.41':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
|
|
||||||
'@types/triple-beam@1.3.5': {}
|
'@types/triple-beam@1.3.5': {}
|
||||||
|
|
||||||
@@ -7726,11 +7816,11 @@ snapshots:
|
|||||||
|
|
||||||
'@types/ws@7.4.7':
|
'@types/ws@7.4.7':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
|
|
||||||
'@types/ws@8.18.1':
|
'@types/ws@8.18.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
|
|
||||||
'@types/yargs-parser@21.0.3': {}
|
'@types/yargs-parser@21.0.3': {}
|
||||||
|
|
||||||
@@ -7740,7 +7830,7 @@ snapshots:
|
|||||||
|
|
||||||
'@types/yauzl@2.10.3':
|
'@types/yauzl@2.10.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@ungap/structured-clone@1.3.0': {}
|
'@ungap/structured-clone@1.3.0': {}
|
||||||
@@ -8376,7 +8466,7 @@ snapshots:
|
|||||||
engine.io@6.6.4:
|
engine.io@6.6.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/cors': 2.8.19
|
'@types/cors': 2.8.19
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
accepts: 1.3.8
|
accepts: 1.3.8
|
||||||
base64id: 2.0.0
|
base64id: 2.0.0
|
||||||
cookie: 0.7.2
|
cookie: 0.7.2
|
||||||
@@ -9112,7 +9202,7 @@ snapshots:
|
|||||||
jest-util@29.7.0:
|
jest-util@29.7.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jest/types': 29.6.3
|
'@jest/types': 29.6.3
|
||||||
'@types/node': 24.10.1
|
'@types/node': 25.0.3
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
ci-info: 3.9.0
|
ci-info: 3.9.0
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
@@ -9321,6 +9411,8 @@ snapshots:
|
|||||||
|
|
||||||
lucide@0.555.0: {}
|
lucide@0.555.0: {}
|
||||||
|
|
||||||
|
lucide@0.562.0: {}
|
||||||
|
|
||||||
make-dir@3.1.0:
|
make-dir@3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
semver: 6.3.1
|
semver: 6.3.1
|
||||||
|
|||||||
122
readme.hints.md
122
readme.hints.md
@@ -680,4 +680,124 @@ According to Lit's documentation (https://lit.dev/docs/components/decorators/#de
|
|||||||
- All unit tests passing
|
- All unit tests passing
|
||||||
- Manual testing of key components verified
|
- Manual testing of key components verified
|
||||||
- No regressions detected
|
- No regressions detected
|
||||||
- Focus management and interactions working correctly
|
- Focus management and interactions working correctly
|
||||||
|
|
||||||
|
## Enhanced AppUI API (2025-12-08)
|
||||||
|
|
||||||
|
The `dees-appui-base` component has been enhanced with a unified configuration API for building real-world applications.
|
||||||
|
|
||||||
|
### New Modules:
|
||||||
|
|
||||||
|
1. **ViewRegistry** (`view.registry.ts`)
|
||||||
|
- Manages view definitions and their lifecycle
|
||||||
|
- Supports tag names, element classes, and template functions as view content
|
||||||
|
- Methods: register, get, renderView, findByRoute
|
||||||
|
|
||||||
|
2. **AppRouter** (`app.router.ts`)
|
||||||
|
- Built-in routing with hash or history mode
|
||||||
|
- External router support for framework integration
|
||||||
|
- Methods: navigate, back, forward, onRouteChange
|
||||||
|
|
||||||
|
3. **StateManager** (`state.manager.ts`)
|
||||||
|
- Persists UI state (collapsed menus, selections, current view)
|
||||||
|
- Supports localStorage, sessionStorage, or memory storage
|
||||||
|
- Methods: save, load, update, clear
|
||||||
|
|
||||||
|
### New Interfaces (in `interfaces/appconfig.ts`):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface IAppConfig {
|
||||||
|
branding?: { logoIcon?: string; logoText?: string };
|
||||||
|
appBar?: IAppBarConfig;
|
||||||
|
views: IViewDefinition[];
|
||||||
|
mainMenu?: IMainMenuConfig;
|
||||||
|
routing?: IRoutingConfig;
|
||||||
|
statePersistence?: IStatePersistenceConfig;
|
||||||
|
onViewChange?: (viewId: string, view: IViewDefinition) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IViewDefinition {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
iconName?: string;
|
||||||
|
content: string | (new () => HTMLElement) | (() => TemplateResult);
|
||||||
|
secondaryMenu?: ISecondaryMenuGroup[];
|
||||||
|
contentTabs?: ITab[];
|
||||||
|
route?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IRoutingConfig {
|
||||||
|
mode: 'hash' | 'history' | 'external' | 'none';
|
||||||
|
basePath?: string;
|
||||||
|
defaultView?: string;
|
||||||
|
syncUrl?: boolean;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### New Public Methods on DeesAppuiBase:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Configure with unified config
|
||||||
|
configure(config: IAppConfig): void
|
||||||
|
|
||||||
|
// Navigation
|
||||||
|
navigateToView(viewId: string): boolean
|
||||||
|
getCurrentView(): IViewDefinition | undefined
|
||||||
|
|
||||||
|
// State management
|
||||||
|
getUIState(): IAppUIState
|
||||||
|
restoreUIState(state: IAppUIState): void
|
||||||
|
saveState(): void
|
||||||
|
loadState(): boolean
|
||||||
|
|
||||||
|
// Access internals
|
||||||
|
getViewRegistry(): ViewRegistry
|
||||||
|
getRouter(): AppRouter | null
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usage Example (New Unified Config API):
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type { IAppConfig } from '@design.estate/dees-catalog';
|
||||||
|
|
||||||
|
const config: IAppConfig = {
|
||||||
|
branding: { logoIcon: 'lucide:box', logoText: 'My App' },
|
||||||
|
views: [
|
||||||
|
{ id: 'dashboard', name: 'Dashboard', iconName: 'lucide:home', content: 'my-dashboard' },
|
||||||
|
{ id: 'settings', name: 'Settings', iconName: 'lucide:settings', content: 'my-settings' },
|
||||||
|
],
|
||||||
|
mainMenu: {
|
||||||
|
sections: [{ views: ['dashboard'] }],
|
||||||
|
bottomItems: ['settings'],
|
||||||
|
},
|
||||||
|
routing: { mode: 'hash', defaultView: 'dashboard' },
|
||||||
|
statePersistence: { enabled: true, storage: 'localStorage' },
|
||||||
|
};
|
||||||
|
|
||||||
|
html`<dees-appui-base .config=${config}></dees-appui-base>`;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backward Compatibility:
|
||||||
|
|
||||||
|
The existing property-based API still works:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
html`
|
||||||
|
<dees-appui-base
|
||||||
|
.mainmenuGroups=${groups}
|
||||||
|
.secondarymenuGroups=${secondaryGroups}
|
||||||
|
@mainmenu-tab-select=${handler}
|
||||||
|
>
|
||||||
|
<div slot="maincontent">...</div>
|
||||||
|
</dees-appui-base>
|
||||||
|
`;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Features:
|
||||||
|
|
||||||
|
- **Declarative View Registry**: Map menu items to view components
|
||||||
|
- **Built-in Routing**: Hash or history mode with URL synchronization
|
||||||
|
- **External Router Support**: Integrate with Angular Router or other frameworks
|
||||||
|
- **State Persistence**: Save/restore collapsed menus, selections, and current view
|
||||||
|
- **View-specific Menus**: Each view can define its own secondary menu and tabs
|
||||||
|
- **Full Backward Compatibility**: Existing code continues to work
|
||||||
116
readme.md
116
readme.md
@@ -582,78 +582,58 @@ Submit button component specifically designed for `DeesForm`.
|
|||||||
### Layout Components
|
### Layout Components
|
||||||
|
|
||||||
#### `DeesAppuiBase`
|
#### `DeesAppuiBase`
|
||||||
Base container component for application layout structure with integrated appbar, menu system, and content areas.
|
A comprehensive application shell component providing a complete UI framework with navigation, menus, activity logging, and view management.
|
||||||
|
|
||||||
|
> **Full API Documentation**: See [ts_web/elements/00group-appui/dees-appui-base/readme.md](./ts_web/elements/00group-appui/dees-appui-base/readme.md) for complete documentation including all programmatic APIs, view lifecycle hooks, and TypeScript interfaces.
|
||||||
|
|
||||||
|
**Quick Start:**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
<dees-appui-base
|
import { html, DeesElement, customElement } from '@design.estate/dees-element';
|
||||||
// Appbar configuration
|
import { DeesAppuiBase } from '@design.estate/dees-catalog';
|
||||||
.appbarMenuItems=${[
|
|
||||||
{
|
@customElement('my-app')
|
||||||
name: 'File',
|
class MyApp extends DeesElement {
|
||||||
action: async () => {}, // No-op for parent menu items
|
private appui: DeesAppuiBase;
|
||||||
submenu: [
|
|
||||||
{ name: 'New File', shortcut: 'Cmd+N', iconName: 'file-plus', action: async () => {} },
|
async firstUpdated() {
|
||||||
{ name: 'Open...', shortcut: 'Cmd+O', iconName: 'folder-open', action: async () => {} },
|
this.appui = this.shadowRoot.querySelector('dees-appui-base');
|
||||||
{ divider: true },
|
|
||||||
{ name: 'Save', shortcut: 'Cmd+S', iconName: 'save', action: async () => {} }
|
// Configure with views and menu
|
||||||
]
|
this.appui.configure({
|
||||||
},
|
branding: { logoIcon: 'lucide:box', logoText: 'My App' },
|
||||||
{
|
views: [
|
||||||
name: 'Edit',
|
{ id: 'dashboard', name: 'Dashboard', iconName: 'lucide:home', content: 'my-dashboard' },
|
||||||
action: async () => {},
|
{ id: 'settings', name: 'Settings', iconName: 'lucide:settings', content: 'my-settings' },
|
||||||
submenu: [
|
],
|
||||||
{ name: 'Undo', shortcut: 'Cmd+Z', iconName: 'undo', action: async () => {} },
|
mainMenu: {
|
||||||
{ name: 'Redo', shortcut: 'Cmd+Shift+Z', iconName: 'redo', action: async () => {} }
|
sections: [{ name: 'Main', views: ['dashboard', 'settings'] }]
|
||||||
]
|
},
|
||||||
}
|
defaultView: 'dashboard'
|
||||||
]}
|
});
|
||||||
.appbarBreadcrumbs=${'Dashboard > Overview'}
|
}
|
||||||
.appbarTheme=${'dark'}
|
|
||||||
.appbarUser=${{ name: 'John Doe', status: 'online' }}
|
render() {
|
||||||
.appbarShowSearch=${true}
|
return html`<dees-appui-base></dees-appui-base>`;
|
||||||
.appbarShowWindowControls=${true}
|
}
|
||||||
|
}
|
||||||
// Main menu configuration (left sidebar)
|
|
||||||
.mainmenuTabs=${[
|
|
||||||
{ key: 'dashboard', iconName: 'lucide:home', action: () => {} },
|
|
||||||
{ key: 'projects', iconName: 'lucide:folder', action: () => {} },
|
|
||||||
{ key: 'settings', iconName: 'lucide:settings', action: () => {} }
|
|
||||||
]}
|
|
||||||
.mainmenuSelectedTab=${selectedTab}
|
|
||||||
|
|
||||||
// Selector configuration (second sidebar)
|
|
||||||
.mainselectorOptions=${[
|
|
||||||
{ key: 'Overview', action: () => {} },
|
|
||||||
{ key: 'Components', action: () => {} },
|
|
||||||
{ key: 'Services', action: () => {} }
|
|
||||||
]}
|
|
||||||
.mainselectorSelectedOption=${selectedOption}
|
|
||||||
|
|
||||||
// Main content tabs
|
|
||||||
.maincontentTabs=${[
|
|
||||||
{ key: 'tab1', iconName: 'lucide:file', action: () => {} }
|
|
||||||
]}
|
|
||||||
|
|
||||||
// Event handlers
|
|
||||||
@appbar-menu-select=${(e) => handleMenuSelect(e.detail)}
|
|
||||||
@appbar-breadcrumb-navigate=${(e) => handleBreadcrumbNav(e.detail)}
|
|
||||||
@appbar-search-click=${() => handleSearch()}
|
|
||||||
@appbar-user-menu-open=${() => handleUserMenu()}
|
|
||||||
@mainmenu-tab-select=${(e) => handleTabSelect(e.detail)}
|
|
||||||
@mainselector-option-select=${(e) => handleOptionSelect(e.detail)}
|
|
||||||
>
|
|
||||||
<div slot="maincontent">
|
|
||||||
<!-- Your main application content goes here -->
|
|
||||||
</div>
|
|
||||||
</dees-appui-base>
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Key Features:
|
**Key Features:**
|
||||||
- **Integrated Layout System**: Automatically arranges appbar, sidebars, and content area
|
- **Configure API**: Single `configure()` method for complete app setup
|
||||||
- **Centralized Configuration**: Pass properties to all child components from one place
|
- **View Management**: Automatic view caching, lazy loading, and lifecycle hooks
|
||||||
- **Event Propagation**: All child component events are re-emitted for easy handling
|
- **Programmatic APIs**: Full control over AppBar, Main Menu, Secondary Menu, Content Tabs, and Activity Log
|
||||||
- **Responsive Grid**: Uses CSS Grid for flexible, responsive layout
|
- **View Lifecycle Hooks**: `onActivate()`, `onDeactivate()`, and `canDeactivate()` for view components
|
||||||
- **Slot Support**: Main content area supports custom content via slots
|
- **Hash-based Routing**: Automatic URL synchronization with view navigation
|
||||||
|
- **RxJS Observables**: `viewChanged$` and `viewLifecycle$` for reactive programming
|
||||||
|
- **TypeScript-first**: Typed `IViewActivationContext` passed to views on activation
|
||||||
|
|
||||||
|
**Programmatic APIs include:**
|
||||||
|
- `navigateToView(viewId, params?)` - Navigate between views
|
||||||
|
- `setAppBarMenus()`, `setBreadcrumbs()`, `setUser()` - Control the app bar
|
||||||
|
- `setMainMenu()`, `setMainMenuSelection()`, `setMainMenuBadge()` - Control main navigation
|
||||||
|
- `setSecondaryMenu()`, `setContentTabs()` - Control view-specific UI
|
||||||
|
- `activityLog.add()`, `activityLog.addMany()`, `activityLog.clear()` - Manage activity entries
|
||||||
|
|
||||||
#### `DeesAppuiMainmenu`
|
#### `DeesAppuiMainmenu`
|
||||||
Main navigation menu component for application-wide navigation.
|
Main navigation menu component for application-wide navigation.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js';
|
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||||
import { demoFunc } from '../ts_web/elements/dees-contextmenu.demo.js';
|
import { demoFunc } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.demo.js';
|
||||||
|
|
||||||
tap.test('should render context menu demo', async () => {
|
tap.test('should render context menu demo', async () => {
|
||||||
// Create demo container
|
// Create demo container
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js';
|
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||||
|
|
||||||
tap.test('should close all parent menus when clicking action in nested submenu', async () => {
|
tap.test('should close all parent menus when clicking action in nested submenu', async () => {
|
||||||
let actionCalled = false;
|
let actionCalled = false;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js';
|
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||||
import { DeesElement, customElement, html } from '@design.estate/dees-element';
|
import { DeesElement, customElement, html } from '@design.estate/dees-element';
|
||||||
|
|
||||||
// Create a test element with shadow DOM
|
// Create a test element with shadow DOM
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js';
|
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||||
|
|
||||||
tap.test('should show context menu with nested submenu', async () => {
|
tap.test('should show context menu with nested submenu', async () => {
|
||||||
// Create a test element with context menu items
|
// Create a test element with context menu items
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, tap, webhelpers } from '@push.rocks/tapbundle';
|
import { expect, tap, webhelpers } from '@push.rocks/tapbundle';
|
||||||
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
|
||||||
import { WysiwygSelection } from '../ts_web/elements/wysiwyg/wysiwyg.selection.js';
|
import { WysiwygSelection } from '../ts_web/elements/00group-input/dees-input-wysiwyg/wysiwyg.selection.js';
|
||||||
|
|
||||||
tap.test('Shadow DOM containment should work correctly', async () => {
|
tap.test('Shadow DOM containment should work correctly', async () => {
|
||||||
console.log('=== Testing Shadow DOM Containment ===');
|
console.log('=== Testing Shadow DOM Containment ===');
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
|
|
||||||
tap.test('should create wysiwyg editor', async () => {
|
tap.test('should create wysiwyg editor', async () => {
|
||||||
const editor = new DeesInputWysiwyg();
|
const editor = new DeesInputWysiwyg();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
|
|
||||||
// Initialize the element
|
// Initialize the element
|
||||||
DeesInputWysiwyg;
|
DeesInputWysiwyg;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { tap, expect, webhelpers } from '@push.rocks/tapbundle';
|
import { tap, expect, webhelpers } from '@push.rocks/tapbundle';
|
||||||
|
|
||||||
import * as deesCatalog from '../ts_web/index.js';
|
import * as deesCatalog from '../ts_web/index.js';
|
||||||
import { BlockRegistry } from '../ts_web/elements/wysiwyg/blocks/block.registry.js';
|
import { BlockRegistry } from '../ts_web/elements/00group-input/dees-input-wysiwyg/blocks/block.registry.js';
|
||||||
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
|
||||||
|
|
||||||
// Import block registration to ensure handlers are registered
|
// Import block registration to ensure handlers are registered
|
||||||
import '../ts_web/elements/wysiwyg/wysiwyg.blockregistration.js';
|
import '../ts_web/elements/00group-input/dees-input-wysiwyg/wysiwyg.blockregistration.js';
|
||||||
|
|
||||||
tap.test('Debug: should create empty wysiwyg block component', async () => {
|
tap.test('Debug: should create empty wysiwyg block component', async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { tap, expect, webhelpers } from '@push.rocks/tapbundle';
|
import { tap, expect, webhelpers } from '@push.rocks/tapbundle';
|
||||||
|
|
||||||
import * as deesCatalog from '../ts_web/index.js';
|
import * as deesCatalog from '../ts_web/index.js';
|
||||||
import { BlockRegistry } from '../ts_web/elements/wysiwyg/blocks/block.registry.js';
|
import { BlockRegistry } from '../ts_web/elements/00group-input/dees-input-wysiwyg/blocks/block.registry.js';
|
||||||
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
|
||||||
|
|
||||||
// Import block registration to ensure handlers are registered
|
// Import block registration to ensure handlers are registered
|
||||||
import '../ts_web/elements/wysiwyg/wysiwyg.blockregistration.js';
|
import '../ts_web/elements/00group-input/dees-input-wysiwyg/wysiwyg.blockregistration.js';
|
||||||
|
|
||||||
tap.test('BlockRegistry should have registered handlers', async () => {
|
tap.test('BlockRegistry should have registered handlers', async () => {
|
||||||
// Test divider handler
|
// Test divider handler
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js';
|
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||||
|
|
||||||
tap.test('should change block type via context menu', async () => {
|
tap.test('should change block type via context menu', async () => {
|
||||||
// Create WYSIWYG editor with a paragraph
|
// Create WYSIWYG editor with a paragraph
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu.js';
|
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||||
|
|
||||||
tap.test('should show context menu on WYSIWYG blocks', async () => {
|
tap.test('should show context menu on WYSIWYG blocks', async () => {
|
||||||
// Create WYSIWYG editor
|
// Create WYSIWYG editor
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
|
|
||||||
// Initialize the element
|
// Initialize the element
|
||||||
DeesInputWysiwyg;
|
DeesInputWysiwyg;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
|
|
||||||
// Initialize the element
|
// Initialize the element
|
||||||
DeesInputWysiwyg;
|
DeesInputWysiwyg;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
|
|
||||||
// Initialize the element
|
// Initialize the element
|
||||||
DeesInputWysiwyg;
|
DeesInputWysiwyg;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
|
|
||||||
// Initialize the element
|
// Initialize the element
|
||||||
DeesInputWysiwyg;
|
DeesInputWysiwyg;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
|
|
||||||
// Initialize the element
|
// Initialize the element
|
||||||
DeesInputWysiwyg;
|
DeesInputWysiwyg;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
|
|
||||||
// Initialize the element
|
// Initialize the element
|
||||||
DeesInputWysiwyg;
|
DeesInputWysiwyg;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
|
||||||
|
|
||||||
tap.test('Keyboard: Arrow navigation between blocks', async () => {
|
tap.test('Keyboard: Arrow navigation between blocks', async () => {
|
||||||
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
|
||||||
|
|
||||||
tap.test('Phase 3: Quote block should render and work correctly', async () => {
|
tap.test('Phase 3: Quote block should render and work correctly', async () => {
|
||||||
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { tap, expect, webhelpers } from '@git.zone/tstest/tapbundle';
|
import { tap, expect, webhelpers } from '@git.zone/tstest/tapbundle';
|
||||||
|
|
||||||
import { BlockRegistry } from '../ts_web/elements/wysiwyg/blocks/block.registry.js';
|
import { BlockRegistry } from '../ts_web/elements/00group-input/dees-input-wysiwyg/blocks/block.registry.js';
|
||||||
import { DividerBlockHandler } from '../ts_web/elements/wysiwyg/blocks/content/divider.block.js';
|
import { DividerBlockHandler } from '../ts_web/elements/00group-input/dees-input-wysiwyg/blocks/content/divider.block.js';
|
||||||
import { ParagraphBlockHandler } from '../ts_web/elements/wysiwyg/blocks/text/paragraph.block.js';
|
import { ParagraphBlockHandler } from '../ts_web/elements/00group-input/dees-input-wysiwyg/blocks/text/paragraph.block.js';
|
||||||
import { HeadingBlockHandler } from '../ts_web/elements/wysiwyg/blocks/text/heading.block.js';
|
import { HeadingBlockHandler } from '../ts_web/elements/00group-input/dees-input-wysiwyg/blocks/text/heading.block.js';
|
||||||
|
|
||||||
// Import block registration to ensure handlers are registered
|
// Import block registration to ensure handlers are registered
|
||||||
import '../ts_web/elements/wysiwyg/wysiwyg.blockregistration.js';
|
import '../ts_web/elements/00group-input/dees-input-wysiwyg/wysiwyg.blockregistration.js';
|
||||||
|
|
||||||
tap.test('BlockRegistry should register and retrieve handlers', async () => {
|
tap.test('BlockRegistry should register and retrieve handlers', async () => {
|
||||||
// Test divider handler
|
// Test divider handler
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
|
||||||
|
|
||||||
tap.test('Selection highlighting should work consistently for all block types', async () => {
|
tap.test('Selection highlighting should work consistently for all block types', async () => {
|
||||||
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
|
||||||
|
|
||||||
tap.test('Selection highlighting basic test', async () => {
|
tap.test('Selection highlighting basic test', async () => {
|
||||||
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
const editor: DeesInputWysiwyg = await webhelpers.fixture(
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { tap, expect, webhelpers } from '@git.zone/tstest/tapbundle';
|
import { tap, expect, webhelpers } from '@git.zone/tstest/tapbundle';
|
||||||
|
|
||||||
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
|
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||||
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
|
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
|
||||||
|
|
||||||
tap.test('should split paragraph content on Enter key', async () => {
|
tap.test('should split paragraph content on Enter key', async () => {
|
||||||
// Create the wysiwyg editor
|
// Create the wysiwyg editor
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@design.estate/dees-catalog',
|
name: '@design.estate/dees-catalog',
|
||||||
version: '3.1.1',
|
version: '3.4.0',
|
||||||
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import * as plugins from '../../00plugins.js';
|
|
||||||
import {
|
import {
|
||||||
DeesElement,
|
DeesElement,
|
||||||
type TemplateResult,
|
type TemplateResult,
|
||||||
@@ -7,32 +6,74 @@ import {
|
|||||||
html,
|
html,
|
||||||
css,
|
css,
|
||||||
cssManager,
|
cssManager,
|
||||||
|
state,
|
||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
import * as domtools from '@design.estate/dees-domtools';
|
import * as domtools from '@design.estate/dees-domtools';
|
||||||
import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
|
import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
|
||||||
import '../../dees-icon/dees-icon.js';
|
import '../../dees-icon/dees-icon.js';
|
||||||
|
import '@design.estate/dees-wcctools/demotools';
|
||||||
|
import type { IActivityEntry, IActivityLogAPI } from '../../interfaces/appconfig.js';
|
||||||
|
|
||||||
@customElement('dees-appui-activitylog')
|
@customElement('dees-appui-activitylog')
|
||||||
export class DeesAppuiActivitylog extends DeesElement {
|
export class DeesAppuiActivitylog extends DeesElement implements IActivityLogAPI {
|
||||||
// STATIC
|
// STATIC
|
||||||
public static demo = () => html`
|
public static demo = () => {
|
||||||
<style>
|
// Create the activity log element
|
||||||
.demo-container {
|
const activityLog = document.createElement('dees-appui-activitylog') as DeesAppuiActivitylog;
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 600px;
|
|
||||||
background: ${cssManager.bdTheme('#f4f4f5', '#09090b')};
|
|
||||||
padding: 32px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<div class="demo-container">
|
|
||||||
<dees-appui-activitylog></dees-appui-activitylog>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// INSTANCE
|
// Add demo entries after the element is connected
|
||||||
|
setTimeout(() => {
|
||||||
|
activityLog.addMany([
|
||||||
|
{ type: 'login', user: 'John Doe', message: 'logged in from Chrome on macOS' },
|
||||||
|
{ type: 'create', user: 'John Doe', message: 'created a new project "Frontend App"' },
|
||||||
|
{ type: 'update', user: 'Jane Smith', message: 'updated API documentation' },
|
||||||
|
{ type: 'view', user: 'John Doe', message: 'viewed dashboard analytics' },
|
||||||
|
{ type: 'delete', user: 'Admin', message: 'removed deprecated endpoint' },
|
||||||
|
{ type: 'custom', user: 'System', message: 'scheduled backup completed', iconName: 'lucide:database' },
|
||||||
|
{ type: 'logout', user: 'Alice Brown', message: 'logged out' },
|
||||||
|
{ type: 'create', user: 'Jane Smith', message: 'created invoice #1234' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Subscribe to updates
|
||||||
|
activityLog.entries$.subscribe((entries) => {
|
||||||
|
console.log('Activity log updated:', entries.length, 'entries');
|
||||||
|
});
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<dees-demowrapper>
|
||||||
|
<style>
|
||||||
|
.demo-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 600px;
|
||||||
|
background: ${cssManager.bdTheme('#f4f4f5', '#09090b')};
|
||||||
|
padding: 32px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="demo-container">
|
||||||
|
${activityLog}
|
||||||
|
</div>
|
||||||
|
</dees-demowrapper>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
// INSTANCE PROPERTIES
|
||||||
|
@state()
|
||||||
|
accessor entries: IActivityEntry[] = [];
|
||||||
|
|
||||||
|
@state()
|
||||||
|
accessor searchQuery: string = '';
|
||||||
|
|
||||||
|
@state()
|
||||||
|
accessor filterCriteria: { user?: string; type?: IActivityEntry['type'] } = {};
|
||||||
|
|
||||||
|
// RxJS Subject for reactive updates
|
||||||
|
public entries$ = new domtools.plugins.smartrx.rxjs.Subject<IActivityEntry[]>();
|
||||||
|
|
||||||
|
// STYLES
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
@@ -90,24 +131,32 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-color: ${cssManager.bdTheme('#e5e7eb', '#27272a')} transparent;
|
scrollbar-color: ${cssManager.bdTheme('#e5e7eb', '#27272a')} transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.activityContainer::-webkit-scrollbar {
|
.activityContainer::-webkit-scrollbar {
|
||||||
width: 6px;
|
width: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.activityContainer::-webkit-scrollbar-track {
|
.activityContainer::-webkit-scrollbar-track {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.activityContainer::-webkit-scrollbar-thumb {
|
.activityContainer::-webkit-scrollbar-thumb {
|
||||||
background: ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
background: ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.activityContainer::-webkit-scrollbar-thumb:hover {
|
.activityContainer::-webkit-scrollbar-thumb:hover {
|
||||||
background: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')};
|
background: ${cssManager.bdTheme('#d4d4d8', '#3f3f46')};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 32px 16px;
|
||||||
|
color: ${cssManager.bdTheme('#71717a', '#71717a')};
|
||||||
|
font-family: 'Geist Sans', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
.streamingIndicator {
|
.streamingIndicator {
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -122,7 +171,7 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.streamingIndicator::before {
|
.streamingIndicator::before {
|
||||||
content: '';
|
content: '';
|
||||||
width: 6px;
|
width: 6px;
|
||||||
@@ -131,15 +180,24 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
animation: pulse 2s ease-in-out infinite;
|
animation: pulse 2s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0%, 100% { opacity: 0.4; transform: scale(0.8); }
|
0%, 100% { opacity: 0.4; transform: scale(0.8); }
|
||||||
50% { opacity: 1; transform: scale(1.2); }
|
50% { opacity: 1; transform: scale(1.2); }
|
||||||
}
|
}
|
||||||
|
|
||||||
.streamingIndicator.bottom {
|
.date-separator {
|
||||||
padding-top: 8px;
|
padding: 12px 16px 8px;
|
||||||
padding-bottom: 16px;
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: ${cssManager.bdTheme('#71717a', '#71717a')};
|
||||||
|
background: ${cssManager.bdTheme('#f9fafb', '#09090b')};
|
||||||
|
border-bottom: 1px solid ${cssManager.bdTheme('#f4f4f5', '#18181b')};
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.activityentry {
|
.activityentry {
|
||||||
@@ -154,7 +212,7 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
animation: fadeIn 0.3s ease-out;
|
animation: fadeIn 0.3s ease-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@@ -182,7 +240,7 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
min-width: 45px;
|
min-width: 45px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.activity-icon {
|
.activity-icon {
|
||||||
width: 28px;
|
width: 28px;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
@@ -194,55 +252,51 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.activity-icon.login {
|
.activity-icon.login {
|
||||||
background: ${cssManager.bdTheme('rgba(34, 197, 94, 0.1)', 'rgba(34, 197, 94, 0.1)')};
|
background: ${cssManager.bdTheme('rgba(34, 197, 94, 0.1)', 'rgba(34, 197, 94, 0.1)')};
|
||||||
color: ${cssManager.bdTheme('#16a34a', '#22c55e')};
|
color: ${cssManager.bdTheme('#16a34a', '#22c55e')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.activity-icon.logout {
|
.activity-icon.logout {
|
||||||
background: ${cssManager.bdTheme('rgba(239, 68, 68, 0.1)', 'rgba(239, 68, 68, 0.1)')};
|
background: ${cssManager.bdTheme('rgba(239, 68, 68, 0.1)', 'rgba(239, 68, 68, 0.1)')};
|
||||||
color: ${cssManager.bdTheme('#dc2626', '#ef4444')};
|
color: ${cssManager.bdTheme('#dc2626', '#ef4444')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.activity-icon.view {
|
.activity-icon.view {
|
||||||
background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.1)', 'rgba(59, 130, 246, 0.1)')};
|
background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.1)', 'rgba(59, 130, 246, 0.1)')};
|
||||||
color: ${cssManager.bdTheme('#2563eb', '#3b82f6')};
|
color: ${cssManager.bdTheme('#2563eb', '#3b82f6')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.activity-icon.create {
|
.activity-icon.create {
|
||||||
background: ${cssManager.bdTheme('rgba(168, 85, 247, 0.1)', 'rgba(168, 85, 247, 0.1)')};
|
background: ${cssManager.bdTheme('rgba(168, 85, 247, 0.1)', 'rgba(168, 85, 247, 0.1)')};
|
||||||
color: ${cssManager.bdTheme('#9333ea', '#a855f7')};
|
color: ${cssManager.bdTheme('#9333ea', '#a855f7')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.activity-icon.update {
|
.activity-icon.update {
|
||||||
background: ${cssManager.bdTheme('rgba(251, 146, 60, 0.1)', 'rgba(251, 146, 60, 0.1)')};
|
background: ${cssManager.bdTheme('rgba(251, 146, 60, 0.1)', 'rgba(251, 146, 60, 0.1)')};
|
||||||
color: ${cssManager.bdTheme('#ea580c', '#fb923c')};
|
color: ${cssManager.bdTheme('#ea580c', '#fb923c')};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.activity-icon.delete {
|
||||||
|
background: ${cssManager.bdTheme('rgba(239, 68, 68, 0.1)', 'rgba(239, 68, 68, 0.1)')};
|
||||||
|
color: ${cssManager.bdTheme('#dc2626', '#ef4444')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-icon.custom {
|
||||||
|
background: ${cssManager.bdTheme('rgba(100, 116, 139, 0.1)', 'rgba(100, 116, 139, 0.1)')};
|
||||||
|
color: ${cssManager.bdTheme('#475569', '#94a3b8')};
|
||||||
|
}
|
||||||
|
|
||||||
.activity-text {
|
.activity-text {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
color: ${cssManager.bdTheme('#18181b', '#e4e4e7')};
|
color: ${cssManager.bdTheme('#18181b', '#e4e4e7')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.activity-user {
|
.activity-user {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.date-separator {
|
|
||||||
padding: 12px 16px 8px;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
color: ${cssManager.bdTheme('#71717a', '#71717a')};
|
|
||||||
background: ${cssManager.bdTheme('#f9fafb', '#09090b')};
|
|
||||||
border-bottom: 1px solid ${cssManager.bdTheme('#f4f4f5', '#18181b')};
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.searchbox {
|
.searchbox {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -253,13 +307,13 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
border-top: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
border-top: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-wrapper {
|
.search-wrapper {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-icon {
|
.search-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 10px;
|
left: 10px;
|
||||||
@@ -270,7 +324,7 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
transition: color 0.15s ease;
|
transition: color 0.15s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.searchbox input {
|
.searchbox input {
|
||||||
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
||||||
background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
|
background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
|
||||||
@@ -293,7 +347,7 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
border-color: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
|
border-color: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
|
||||||
box-shadow: 0 0 0 3px ${cssManager.bdTheme('rgba(59, 130, 246, 0.1)', 'rgba(59, 130, 246, 0.1)')};
|
box-shadow: 0 0 0 3px ${cssManager.bdTheme('rgba(59, 130, 246, 0.1)', 'rgba(59, 130, 246, 0.1)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.searchbox input:focus ~ .search-icon,
|
.searchbox input:focus ~ .search-icon,
|
||||||
.search-wrapper:has(input:focus) .search-icon {
|
.search-wrapper:has(input:focus) .search-icon {
|
||||||
color: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
|
color: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
|
||||||
@@ -311,7 +365,7 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.topShadow {
|
.topShadow {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -327,7 +381,11 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// RENDER
|
||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
|
const filteredEntries = this.getFilteredEntries();
|
||||||
|
const groupedEntries = this.groupEntriesByDate(filteredEntries);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${domtools.elementBasic.styles}
|
${domtools.elementBasic.styles}
|
||||||
<style></style>
|
<style></style>
|
||||||
@@ -336,173 +394,28 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
<div class="heading">Activity Log</div>
|
<div class="heading">Activity Log</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="activityContainer">
|
<div class="activityContainer">
|
||||||
<div class="streamingIndicator">Live Updates</div>
|
${filteredEntries.length > 0
|
||||||
|
? html`<div class="streamingIndicator">Live Updates</div>`
|
||||||
<div class="date-separator">Today</div>
|
: ''}
|
||||||
|
|
||||||
<div class="activityentry" @contextmenu=${async eventArg => {
|
${filteredEntries.length === 0
|
||||||
DeesContextmenu.openContextMenuWithOptions(eventArg, [
|
? html`<div class="empty-state">No activity entries</div>`
|
||||||
{
|
: groupedEntries.map(
|
||||||
name: 'Copy activity',
|
(group) => html`
|
||||||
action: async () => {},
|
<div class="date-separator">${group.label}</div>
|
||||||
},
|
${group.entries.map((entry) => this.renderActivityEntry(entry))}
|
||||||
{
|
`
|
||||||
name: 'View details',
|
)}
|
||||||
action: async () => {},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Filter by user',
|
|
||||||
action: async () => {},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}}>
|
|
||||||
<span class="timestamp">22:20</span>
|
|
||||||
<div class="activity-icon logout">
|
|
||||||
<dees-icon .icon=${'lucide:logOut'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> logged out
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">22:19</span>
|
|
||||||
<div class="activity-icon update">
|
|
||||||
<dees-icon .icon=${'lucide:checkCircle'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> approved a payment
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">22:18</span>
|
|
||||||
<div class="activity-icon view">
|
|
||||||
<dees-icon .icon=${'lucide:archive'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> archived an invoice
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">22:17</span>
|
|
||||||
<div class="activity-icon login">
|
|
||||||
<dees-icon .icon=${'lucide:logIn'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> logged in
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">22:16</span>
|
|
||||||
<div class="activity-icon logout">
|
|
||||||
<dees-icon .icon=${'lucide:logOut'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> logged out
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">22:15</span>
|
|
||||||
<div class="activity-icon update">
|
|
||||||
<dees-icon .icon=${'lucide:key'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> changed password
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">22:14</span>
|
|
||||||
<div class="activity-icon create">
|
|
||||||
<dees-icon .icon=${'lucide:userPlus'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> added a new user
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">22:13</span>
|
|
||||||
<div class="activity-icon view">
|
|
||||||
<dees-icon .icon=${'lucide:messageCircle'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> contacted support
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="date-separator">Yesterday</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">18:45</span>
|
|
||||||
<div class="activity-icon update">
|
|
||||||
<dees-icon .icon=${'lucide:trash2'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> deleted an invoice
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">17:30</span>
|
|
||||||
<div class="activity-icon login">
|
|
||||||
<dees-icon .icon=${'lucide:logIn'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> logged in
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">16:15</span>
|
|
||||||
<div class="activity-icon logout">
|
|
||||||
<dees-icon .icon=${'lucide:logOut'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> logged out
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">14:20</span>
|
|
||||||
<div class="activity-icon view">
|
|
||||||
<dees-icon .icon=${'lucide:barChart'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> viewed reports
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">13:45</span>
|
|
||||||
<div class="activity-icon create">
|
|
||||||
<dees-icon .icon=${'lucide:send'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> sent an invoice
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="activityentry">
|
|
||||||
<span class="timestamp">13:30</span>
|
|
||||||
<div class="activity-icon create">
|
|
||||||
<dees-icon .icon=${'lucide:filePlus'}></dees-icon>
|
|
||||||
</div>
|
|
||||||
<div class="activity-text">
|
|
||||||
<span class="activity-user">Max Mustermann</span> created a new invoice
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="streamingIndicator bottom">Loading History</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="searchbox">
|
<div class="searchbox">
|
||||||
<div class="search-wrapper">
|
<div class="search-wrapper">
|
||||||
<dees-icon class="search-icon" .icon=${'lucide:search'}></dees-icon>
|
<dees-icon class="search-icon" .icon=${'lucide:search'}></dees-icon>
|
||||||
<input type="text" placeholder="Search activities, users..." />
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Search activities, users..."
|
||||||
|
.value=${this.searchQuery}
|
||||||
|
@input=${this.handleSearchInput}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="topShadow"></div>
|
<div class="topShadow"></div>
|
||||||
@@ -510,4 +423,205 @@ export class DeesAppuiActivitylog extends DeesElement {
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private renderActivityEntry(entry: IActivityEntry): TemplateResult {
|
||||||
|
const timestamp = entry.timestamp || new Date();
|
||||||
|
const timeStr = this.formatTime(timestamp);
|
||||||
|
const iconName = entry.iconName || this.getIconForType(entry.type);
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="activityentry"
|
||||||
|
@contextmenu=${(e: MouseEvent) => this.handleContextMenu(e, entry)}
|
||||||
|
>
|
||||||
|
<span class="timestamp">${timeStr}</span>
|
||||||
|
<div class="activity-icon ${entry.type}">
|
||||||
|
<dees-icon .icon=${iconName}></dees-icon>
|
||||||
|
</div>
|
||||||
|
<div class="activity-text">
|
||||||
|
<span class="activity-user">${entry.user}</span> ${entry.message}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// API METHODS
|
||||||
|
public add(entry: IActivityEntry): void {
|
||||||
|
const newEntry: IActivityEntry = {
|
||||||
|
...entry,
|
||||||
|
id: entry.id || this.generateId(),
|
||||||
|
timestamp: entry.timestamp || new Date(),
|
||||||
|
};
|
||||||
|
this.entries = [newEntry, ...this.entries];
|
||||||
|
this.entries$.next(this.entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
public addMany(entries: IActivityEntry[]): void {
|
||||||
|
const newEntries = entries.map((entry) => ({
|
||||||
|
...entry,
|
||||||
|
id: entry.id || this.generateId(),
|
||||||
|
timestamp: entry.timestamp || new Date(),
|
||||||
|
}));
|
||||||
|
this.entries = [...newEntries.reverse(), ...this.entries];
|
||||||
|
this.entries$.next(this.entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear(): void {
|
||||||
|
this.entries = [];
|
||||||
|
this.entries$.next(this.entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getEntries(): IActivityEntry[] {
|
||||||
|
return [...this.entries];
|
||||||
|
}
|
||||||
|
|
||||||
|
public filter(criteria: { user?: string; type?: IActivityEntry['type'] }): IActivityEntry[] {
|
||||||
|
return this.entries.filter((entry) => {
|
||||||
|
if (criteria.user && entry.user !== criteria.user) return false;
|
||||||
|
if (criteria.type && entry.type !== criteria.type) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public search(query: string): IActivityEntry[] {
|
||||||
|
const lowerQuery = query.toLowerCase();
|
||||||
|
return this.entries.filter(
|
||||||
|
(entry) =>
|
||||||
|
entry.message.toLowerCase().includes(lowerQuery) ||
|
||||||
|
entry.user.toLowerCase().includes(lowerQuery)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PRIVATE HELPERS
|
||||||
|
private generateId(): string {
|
||||||
|
return `activity-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getFilteredEntries(): IActivityEntry[] {
|
||||||
|
let result = this.entries;
|
||||||
|
|
||||||
|
if (this.searchQuery) {
|
||||||
|
const lowerQuery = this.searchQuery.toLowerCase();
|
||||||
|
result = result.filter(
|
||||||
|
(entry) =>
|
||||||
|
entry.message.toLowerCase().includes(lowerQuery) ||
|
||||||
|
entry.user.toLowerCase().includes(lowerQuery)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.filterCriteria.user || this.filterCriteria.type) {
|
||||||
|
result = result.filter((entry) => {
|
||||||
|
if (this.filterCriteria.user && entry.user !== this.filterCriteria.user) return false;
|
||||||
|
if (this.filterCriteria.type && entry.type !== this.filterCriteria.type) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private groupEntriesByDate(
|
||||||
|
entries: IActivityEntry[]
|
||||||
|
): Array<{ label: string; entries: IActivityEntry[] }> {
|
||||||
|
const groups: Map<string, IActivityEntry[]> = new Map();
|
||||||
|
const today = new Date();
|
||||||
|
const yesterday = new Date(today);
|
||||||
|
yesterday.setDate(yesterday.getDate() - 1);
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const date = entry.timestamp || new Date();
|
||||||
|
let label: string;
|
||||||
|
|
||||||
|
if (this.isSameDay(date, today)) {
|
||||||
|
label = 'Today';
|
||||||
|
} else if (this.isSameDay(date, yesterday)) {
|
||||||
|
label = 'Yesterday';
|
||||||
|
} else {
|
||||||
|
label = date.toLocaleDateString('en-US', {
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
year: date.getFullYear() !== today.getFullYear() ? 'numeric' : undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!groups.has(label)) {
|
||||||
|
groups.set(label, []);
|
||||||
|
}
|
||||||
|
groups.get(label)!.push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(groups.entries()).map(([label, entries]) => ({
|
||||||
|
label,
|
||||||
|
entries,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private isSameDay(date1: Date, date2: Date): boolean {
|
||||||
|
return (
|
||||||
|
date1.getFullYear() === date2.getFullYear() &&
|
||||||
|
date1.getMonth() === date2.getMonth() &&
|
||||||
|
date1.getDate() === date2.getDate()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatTime(date: Date): string {
|
||||||
|
return date.toLocaleTimeString('en-US', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getIconForType(type: IActivityEntry['type']): string {
|
||||||
|
const icons: Record<IActivityEntry['type'], string> = {
|
||||||
|
login: 'lucide:logIn',
|
||||||
|
logout: 'lucide:logOut',
|
||||||
|
view: 'lucide:eye',
|
||||||
|
create: 'lucide:plus',
|
||||||
|
update: 'lucide:edit',
|
||||||
|
delete: 'lucide:trash2',
|
||||||
|
custom: 'lucide:activity',
|
||||||
|
};
|
||||||
|
return icons[type] || icons.custom;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleSearchInput(e: InputEvent): void {
|
||||||
|
const target = e.target as HTMLInputElement;
|
||||||
|
this.searchQuery = target.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleContextMenu(e: MouseEvent, entry: IActivityEntry): void {
|
||||||
|
e.preventDefault();
|
||||||
|
DeesContextmenu.openContextMenuWithOptions(e, [
|
||||||
|
{
|
||||||
|
name: 'Copy activity',
|
||||||
|
iconName: 'lucide:copy',
|
||||||
|
action: async () => {
|
||||||
|
await navigator.clipboard.writeText(`${entry.user} ${entry.message}`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Filter by user',
|
||||||
|
iconName: 'lucide:user',
|
||||||
|
action: async () => {
|
||||||
|
this.filterCriteria = { user: entry.user };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Filter by type',
|
||||||
|
iconName: 'lucide:filter',
|
||||||
|
action: async () => {
|
||||||
|
this.filterCriteria = { type: entry.type };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Clear filters',
|
||||||
|
iconName: 'lucide:x',
|
||||||
|
action: async () => {
|
||||||
|
this.filterCriteria = {};
|
||||||
|
this.searchQuery = '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,238 +1,608 @@
|
|||||||
import { html, css } from '@design.estate/dees-element';
|
import { html, css, DeesElement, customElement, state } from '@design.estate/dees-element';
|
||||||
import type { DeesAppuiBase } from '../dees-appui-base/dees-appui-base.js';
|
import type { DeesAppuiBase } from './dees-appui-base.js';
|
||||||
import type { IAppBarMenuItem } from '../../interfaces/appbarmenuitem.js';
|
import type { IAppConfig, IViewActivationContext } from '../../interfaces/appconfig.js';
|
||||||
import type { ITab } from '../../interfaces/tab.js';
|
|
||||||
import type { ISelectionOption } from '../../interfaces/selectionoption.js';
|
|
||||||
import type { IMenuGroup } from '../../interfaces/menugroup.js';
|
|
||||||
import type { ISecondaryMenuGroup } from '../../interfaces/secondarymenu.js';
|
|
||||||
import * as plugins from '../../00plugins.js';
|
|
||||||
import '@design.estate/dees-wcctools/demotools';
|
import '@design.estate/dees-wcctools/demotools';
|
||||||
|
|
||||||
|
// Demo view component with lifecycle hooks
|
||||||
|
@customElement('demo-dashboard-view')
|
||||||
|
class DemoDashboardView extends DeesElement {
|
||||||
|
@state()
|
||||||
|
accessor activated: boolean = false;
|
||||||
|
|
||||||
|
onActivate(context: IViewActivationContext) {
|
||||||
|
this.activated = true;
|
||||||
|
console.log('Dashboard activated with context:', context);
|
||||||
|
|
||||||
|
// Set view-specific secondary menu
|
||||||
|
context.appui.setSecondaryMenu({
|
||||||
|
heading: 'Dashboard',
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
name: 'Quick Access',
|
||||||
|
iconName: 'lucide:zap',
|
||||||
|
items: [
|
||||||
|
{ key: 'overview', iconName: 'layoutDashboard', action: () => console.log('Overview') },
|
||||||
|
{ key: 'recent', iconName: 'clock', badge: 5, action: () => console.log('Recent') },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Analytics',
|
||||||
|
iconName: 'lucide:barChart3',
|
||||||
|
items: [
|
||||||
|
{ key: 'metrics', iconName: 'activity', action: () => console.log('Metrics') },
|
||||||
|
{ key: 'reports', iconName: 'fileText', badge: 'new', badgeVariant: 'success', action: () => console.log('Reports') },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set content tabs for dashboard
|
||||||
|
context.appui.setContentTabs([
|
||||||
|
{ key: 'Overview', iconName: 'lucide:layoutDashboard', action: () => console.log('Overview tab') },
|
||||||
|
{ key: 'Analytics', iconName: 'lucide:barChart', action: () => console.log('Analytics tab') },
|
||||||
|
{ key: 'Reports', iconName: 'lucide:fileText', action: () => console.log('Reports tab') },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeactivate() {
|
||||||
|
this.activated = false;
|
||||||
|
console.log('Dashboard deactivated');
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding: 40px;
|
||||||
|
color: #a3a3a3;
|
||||||
|
font-family: 'Geist Sans', 'Inter', -apple-system, sans-serif;
|
||||||
|
}
|
||||||
|
h1 { color: #fafafa; font-weight: 600; font-size: 24px; margin-bottom: 8px; }
|
||||||
|
p { color: #737373; margin-bottom: 32px; }
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
background: rgba(255,255,255,0.03);
|
||||||
|
border: 1px solid rgba(255,255,255,0.08);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.card h3 { color: #fafafa; font-size: 14px; font-weight: 600; margin-bottom: 8px; }
|
||||||
|
.metric { font-size: 32px; font-weight: 700; color: #fafafa; }
|
||||||
|
.status { display: inline-block; padding: 2px 8px; border-radius: 9px; font-size: 12px; }
|
||||||
|
.status.active { background: #14532d; color: #4ade80; }
|
||||||
|
</style>
|
||||||
|
<h1>Dashboard</h1>
|
||||||
|
<p>Welcome back! Here's an overview of your system.</p>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="card">
|
||||||
|
<h3>Active Users</h3>
|
||||||
|
<div class="metric">1,234</div>
|
||||||
|
<span class="status active">Online</span>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>API Calls</h3>
|
||||||
|
<div class="metric">45.2K</div>
|
||||||
|
<p style="color: #4ade80; font-size: 12px; margin: 0;">+12% from last hour</p>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>System Health</h3>
|
||||||
|
<div class="metric">99.9%</div>
|
||||||
|
<p style="color: #737373; font-size: 12px; margin: 0;">All systems operational</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings view with route params and canDeactivate guard
|
||||||
|
@customElement('demo-settings-view')
|
||||||
|
class DemoSettingsView extends DeesElement {
|
||||||
|
@state()
|
||||||
|
accessor section: string = 'general';
|
||||||
|
|
||||||
|
@state()
|
||||||
|
accessor hasChanges: boolean = false;
|
||||||
|
|
||||||
|
private appui: DeesAppuiBase;
|
||||||
|
|
||||||
|
onActivate(context: IViewActivationContext) {
|
||||||
|
this.appui = context.appui as any;
|
||||||
|
console.log('Settings activated with params:', context.params);
|
||||||
|
|
||||||
|
if (context.params?.section) {
|
||||||
|
this.section = context.params.section;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set settings-specific secondary menu
|
||||||
|
context.appui.setSecondaryMenu({
|
||||||
|
heading: 'Settings',
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
name: 'Account',
|
||||||
|
iconName: 'lucide:user',
|
||||||
|
items: [
|
||||||
|
{ key: 'general', iconName: 'settings', action: () => this.showSection('general') },
|
||||||
|
{ key: 'profile', iconName: 'user', action: () => this.showSection('profile') },
|
||||||
|
{ key: 'security', iconName: 'shield', action: () => this.showSection('security') },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Preferences',
|
||||||
|
iconName: 'lucide:sliders',
|
||||||
|
items: [
|
||||||
|
{ key: 'notifications', iconName: 'bell', badge: 3, action: () => this.showSection('notifications') },
|
||||||
|
{ key: 'appearance', iconName: 'palette', action: () => this.showSection('appearance') },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
context.appui.setSecondaryMenuSelection(this.section);
|
||||||
|
|
||||||
|
// Clear content tabs for settings
|
||||||
|
context.appui.setContentTabs([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeactivate() {
|
||||||
|
console.log('Settings deactivated');
|
||||||
|
this.hasChanges = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
canDeactivate(): boolean | string {
|
||||||
|
if (this.hasChanges) {
|
||||||
|
return 'You have unsaved changes. Leave anyway?';
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
showSection(section: string) {
|
||||||
|
this.section = section;
|
||||||
|
this.appui?.setSecondaryMenuSelection(section);
|
||||||
|
}
|
||||||
|
|
||||||
|
simulateChange() {
|
||||||
|
this.hasChanges = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding: 40px;
|
||||||
|
color: #a3a3a3;
|
||||||
|
font-family: 'Geist Sans', 'Inter', -apple-system, sans-serif;
|
||||||
|
}
|
||||||
|
h1 { color: #fafafa; font-weight: 600; font-size: 24px; margin-bottom: 8px; }
|
||||||
|
p { color: #737373; margin-bottom: 24px; }
|
||||||
|
.section-name {
|
||||||
|
background: rgba(255,255,255,0.05);
|
||||||
|
border: 1px solid rgba(255,255,255,0.1);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 24px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #fafafa;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background: #2563eb;
|
||||||
|
}
|
||||||
|
.warning {
|
||||||
|
color: #fbbf24;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<h1>Settings</h1>
|
||||||
|
<p>Manage your account and application preferences.</p>
|
||||||
|
<div class="section-name">
|
||||||
|
Current section: <strong>${this.section}</strong>
|
||||||
|
</div>
|
||||||
|
<div class="actions">
|
||||||
|
<button @click=${() => this.simulateChange()}>Make Changes</button>
|
||||||
|
</div>
|
||||||
|
${this.hasChanges ? html`<p class="warning">You have unsaved changes. Navigation will prompt for confirmation.</p>` : ''}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Projects view
|
||||||
|
@customElement('demo-projects-view')
|
||||||
|
class DemoProjectsView extends DeesElement {
|
||||||
|
onActivate(context: IViewActivationContext) {
|
||||||
|
context.appui.setSecondaryMenu({
|
||||||
|
heading: 'Projects',
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
name: 'My Projects',
|
||||||
|
items: [
|
||||||
|
{ key: 'active', iconName: 'folder', badge: 3, action: () => console.log('Active') },
|
||||||
|
{ key: 'archived', iconName: 'archive', action: () => console.log('Archived') },
|
||||||
|
{ key: 'shared', iconName: 'users', badge: 2, badgeVariant: 'warning', action: () => console.log('Shared') },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
context.appui.setContentTabs([
|
||||||
|
{ key: 'Grid', iconName: 'lucide:grid', action: () => console.log('Grid view') },
|
||||||
|
{ key: 'List', iconName: 'lucide:list', action: () => console.log('List view') },
|
||||||
|
{ key: 'Board', iconName: 'lucide:kanban', action: () => console.log('Board view') },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding: 40px;
|
||||||
|
color: #a3a3a3;
|
||||||
|
font-family: 'Geist Sans', 'Inter', -apple-system, sans-serif;
|
||||||
|
}
|
||||||
|
h1 { color: #fafafa; font-weight: 600; font-size: 24px; margin-bottom: 24px; }
|
||||||
|
.projects {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
.project {
|
||||||
|
background: rgba(255,255,255,0.03);
|
||||||
|
border: 1px solid rgba(255,255,255,0.08);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
}
|
||||||
|
.project:hover {
|
||||||
|
border-color: rgba(255,255,255,0.2);
|
||||||
|
}
|
||||||
|
.project h3 { color: #fafafa; margin: 0 0 8px 0; font-size: 16px; }
|
||||||
|
.project p { color: #737373; margin: 0; font-size: 13px; }
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
background: #14532d;
|
||||||
|
color: #4ade80;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 9px;
|
||||||
|
font-size: 11px;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<h1>Projects</h1>
|
||||||
|
<div class="projects">
|
||||||
|
<div class="project">
|
||||||
|
<h3>Frontend App <span class="badge">Active</span></h3>
|
||||||
|
<p>React-based dashboard application</p>
|
||||||
|
</div>
|
||||||
|
<div class="project">
|
||||||
|
<h3>API Server <span class="badge">Active</span></h3>
|
||||||
|
<p>Node.js REST API backend</p>
|
||||||
|
</div>
|
||||||
|
<div class="project">
|
||||||
|
<h3>Mobile App <span class="badge">Active</span></h3>
|
||||||
|
<p>React Native iOS/Android app</p>
|
||||||
|
</div>
|
||||||
|
<div class="project">
|
||||||
|
<h3>Documentation</h3>
|
||||||
|
<p>Technical documentation site</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tasks view showing inline template content
|
||||||
|
@customElement('demo-tasks-view')
|
||||||
|
class DemoTasksView extends DeesElement {
|
||||||
|
onActivate(context: IViewActivationContext) {
|
||||||
|
context.appui.setSecondaryMenu({
|
||||||
|
heading: 'Tasks',
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
name: 'Filters',
|
||||||
|
items: [
|
||||||
|
{ key: 'all', iconName: 'list', badge: 12, action: () => console.log('All') },
|
||||||
|
{ key: 'today', iconName: 'calendar', badge: 3, action: () => console.log('Today') },
|
||||||
|
{ key: 'upcoming', iconName: 'clock', action: () => console.log('Upcoming') },
|
||||||
|
{ key: 'completed', iconName: 'checkCircle', action: () => console.log('Completed') },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
context.appui.setContentTabs([
|
||||||
|
{ key: 'List', iconName: 'lucide:list', action: () => console.log('List') },
|
||||||
|
{ key: 'Calendar', iconName: 'lucide:calendar', action: () => console.log('Calendar') },
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<style>
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding: 40px;
|
||||||
|
color: #a3a3a3;
|
||||||
|
font-family: 'Geist Sans', 'Inter', -apple-system, sans-serif;
|
||||||
|
}
|
||||||
|
h1 { color: #fafafa; font-weight: 600; font-size: 24px; margin-bottom: 24px; }
|
||||||
|
.task-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.task {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
background: rgba(255,255,255,0.03);
|
||||||
|
border: 1px solid rgba(255,255,255,0.08);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
.checkbox {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border: 2px solid #525252;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.task-text { color: #fafafa; flex: 1; }
|
||||||
|
.due-date { color: #737373; font-size: 12px; }
|
||||||
|
.priority {
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.priority.high { background: #450a0a; color: #f87171; }
|
||||||
|
.priority.medium { background: #451a03; color: #fbbf24; }
|
||||||
|
</style>
|
||||||
|
<h1>Tasks</h1>
|
||||||
|
<div class="task-list">
|
||||||
|
<div class="task">
|
||||||
|
<div class="checkbox"></div>
|
||||||
|
<span class="task-text">Review pull request #42</span>
|
||||||
|
<span class="due-date">Today</span>
|
||||||
|
<span class="priority high">High</span>
|
||||||
|
</div>
|
||||||
|
<div class="task">
|
||||||
|
<div class="checkbox"></div>
|
||||||
|
<span class="task-text">Update documentation</span>
|
||||||
|
<span class="due-date">Tomorrow</span>
|
||||||
|
<span class="priority medium">Medium</span>
|
||||||
|
</div>
|
||||||
|
<div class="task">
|
||||||
|
<div class="checkbox"></div>
|
||||||
|
<span class="task-text">Write unit tests</span>
|
||||||
|
<span class="due-date">Dec 20</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const demoFunc = () => {
|
export const demoFunc = () => {
|
||||||
// Menu items for the appbar
|
// App configuration using the new unified API
|
||||||
const menuItems: IAppBarMenuItem[] = [
|
const appConfig: IAppConfig = {
|
||||||
{
|
branding: {
|
||||||
name: 'File',
|
logoIcon: 'lucide:box',
|
||||||
action: async () => {},
|
logoText: 'Acme App'
|
||||||
submenu: [
|
},
|
||||||
{ name: 'New Project', shortcut: 'Cmd+N', iconName: 'filePlus', action: async () => console.log('New project') },
|
|
||||||
{ name: 'Open Project...', shortcut: 'Cmd+O', iconName: 'folderOpen', action: async () => console.log('Open project') },
|
appBar: {
|
||||||
{ name: 'Recent Projects', action: async () => {}, submenu: [
|
menuItems: [
|
||||||
{ name: 'my-app', action: async () => console.log('Open my-app') },
|
{
|
||||||
{ name: 'component-lib', action: async () => console.log('Open component-lib') },
|
name: 'File',
|
||||||
{ name: 'api-server', action: async () => console.log('Open api-server') },
|
action: async () => {},
|
||||||
]},
|
submenu: [
|
||||||
|
{ name: 'New Project', shortcut: 'Cmd+N', iconName: 'filePlus', action: async () => console.log('New') },
|
||||||
|
{ name: 'Open...', shortcut: 'Cmd+O', iconName: 'folderOpen', action: async () => console.log('Open') },
|
||||||
|
{ name: 'Recent Projects', action: async () => {}, submenu: [
|
||||||
|
{ name: 'my-app', action: async () => console.log('Open my-app') },
|
||||||
|
{ name: 'component-lib', action: async () => console.log('Open component-lib') },
|
||||||
|
]},
|
||||||
|
{ divider: true },
|
||||||
|
{ name: 'Save All', shortcut: 'Cmd+S', iconName: 'save', action: async () => console.log('Save') },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Edit',
|
||||||
|
action: async () => {},
|
||||||
|
submenu: [
|
||||||
|
{ name: 'Undo', shortcut: 'Cmd+Z', iconName: 'undo', action: async () => console.log('Undo') },
|
||||||
|
{ name: 'Redo', shortcut: 'Cmd+Shift+Z', iconName: 'redo', action: async () => console.log('Redo') },
|
||||||
|
{ divider: true },
|
||||||
|
{ name: 'Cut', shortcut: 'Cmd+X', iconName: 'scissors', action: async () => console.log('Cut') },
|
||||||
|
{ name: 'Copy', shortcut: 'Cmd+C', iconName: 'copy', action: async () => console.log('Copy') },
|
||||||
|
{ name: 'Paste', shortcut: 'Cmd+V', iconName: 'clipboard', action: async () => console.log('Paste') },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'View',
|
||||||
|
action: async () => {},
|
||||||
|
submenu: [
|
||||||
|
{ name: 'Toggle Sidebar', shortcut: 'Cmd+B', action: async () => console.log('Toggle sidebar') },
|
||||||
|
{ name: 'Toggle Activity Log', shortcut: 'Cmd+Shift+A', action: async () => console.log('Toggle activity') },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Help',
|
||||||
|
action: async () => {},
|
||||||
|
submenu: [
|
||||||
|
{ name: 'Documentation', iconName: 'book', action: async () => console.log('Docs') },
|
||||||
|
{ name: 'Keyboard Shortcuts', iconName: 'keyboard', shortcut: 'Cmd+/', action: async () => console.log('Shortcuts') },
|
||||||
|
{ divider: true },
|
||||||
|
{ name: 'About', iconName: 'info', action: async () => console.log('About') },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
breadcrumbs: 'Dashboard',
|
||||||
|
showWindowControls: true,
|
||||||
|
showSearch: true,
|
||||||
|
user: {
|
||||||
|
name: 'Jane Smith',
|
||||||
|
email: 'jane.smith@example.com',
|
||||||
|
status: 'online'
|
||||||
|
},
|
||||||
|
profileMenuItems: [
|
||||||
|
{ name: 'Profile', iconName: 'user', action: async () => console.log('Profile') },
|
||||||
|
{ name: 'Account Settings', iconName: 'settings', action: async () => console.log('Settings') },
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
{ name: 'Save All', shortcut: 'Cmd+Shift+S', iconName: 'save', action: async () => console.log('Save all') },
|
{ name: 'Help & Support', iconName: 'helpCircle', action: async () => console.log('Help') },
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
{ name: 'Close Project', action: async () => console.log('Close project') },
|
{ name: 'Sign Out', iconName: 'logOut', action: async () => console.log('Sign out') }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'Edit',
|
views: [
|
||||||
action: async () => {},
|
{
|
||||||
submenu: [
|
id: 'dashboard',
|
||||||
{ name: 'Undo', shortcut: 'Cmd+Z', iconName: 'undo', action: async () => console.log('Undo') },
|
name: 'Dashboard',
|
||||||
{ name: 'Redo', shortcut: 'Cmd+Shift+Z', iconName: 'redo', action: async () => console.log('Redo') },
|
iconName: 'lucide:home',
|
||||||
{ divider: true },
|
content: 'demo-dashboard-view',
|
||||||
{ name: 'Cut', shortcut: 'Cmd+X', iconName: 'scissors', action: async () => console.log('Cut') },
|
route: 'dashboard'
|
||||||
{ name: 'Copy', shortcut: 'Cmd+C', iconName: 'copy', action: async () => console.log('Copy') },
|
},
|
||||||
{ name: 'Paste', shortcut: 'Cmd+V', iconName: 'clipboard', action: async () => console.log('Paste') },
|
{
|
||||||
]
|
id: 'projects',
|
||||||
|
name: 'Projects',
|
||||||
|
iconName: 'lucide:folder',
|
||||||
|
content: 'demo-projects-view',
|
||||||
|
route: 'projects',
|
||||||
|
badge: 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tasks',
|
||||||
|
name: 'Tasks',
|
||||||
|
iconName: 'lucide:checkSquare',
|
||||||
|
content: 'demo-tasks-view',
|
||||||
|
route: 'tasks',
|
||||||
|
badge: 12
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'settings',
|
||||||
|
name: 'Settings',
|
||||||
|
iconName: 'lucide:settings',
|
||||||
|
content: 'demo-settings-view',
|
||||||
|
route: 'settings/:section?'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
mainMenu: {
|
||||||
|
sections: [
|
||||||
|
{ name: 'Main', views: ['dashboard'] },
|
||||||
|
{ name: 'Workspace', views: ['projects', 'tasks'] },
|
||||||
|
],
|
||||||
|
bottomItems: ['settings']
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'View',
|
defaultView: 'dashboard',
|
||||||
action: async () => {},
|
|
||||||
submenu: [
|
onViewChange: (viewId, view) => {
|
||||||
{ name: 'Toggle Sidebar', shortcut: 'Cmd+B', action: async () => console.log('Toggle sidebar') },
|
console.log(`View changed to: ${viewId} (${view.name})`);
|
||||||
{ name: 'Toggle Terminal', shortcut: 'Cmd+J', iconName: 'terminal', action: async () => console.log('Toggle terminal') },
|
|
||||||
{ divider: true },
|
|
||||||
{ name: 'Zoom In', shortcut: 'Cmd++', iconName: 'zoomIn', action: async () => console.log('Zoom in') },
|
|
||||||
{ name: 'Zoom Out', shortcut: 'Cmd+-', iconName: 'zoomOut', action: async () => console.log('Zoom out') },
|
|
||||||
{ name: 'Reset Zoom', shortcut: 'Cmd+0', action: async () => console.log('Reset zoom') },
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'Help',
|
onSearch: (query) => {
|
||||||
action: async () => {},
|
console.log('Search query:', query);
|
||||||
submenu: [
|
|
||||||
{ name: 'Documentation', iconName: 'book', action: async () => console.log('Documentation') },
|
|
||||||
{ name: 'Release Notes', iconName: 'fileText', action: async () => console.log('Release notes') },
|
|
||||||
{ divider: true },
|
|
||||||
{ name: 'Report Issue', iconName: 'bug', action: async () => console.log('Report issue') },
|
|
||||||
{ name: 'About', iconName: 'info', action: async () => console.log('About') },
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
];
|
};
|
||||||
|
|
||||||
// Main menu groups (left sidebar)
|
// Use a container element to properly initialize the demo
|
||||||
const mainMenuGroups: IMenuGroup[] = [
|
const containerElement = document.createElement('div');
|
||||||
{
|
containerElement.className = 'demo-container';
|
||||||
tabs: [
|
containerElement.style.cssText = 'position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden;';
|
||||||
{ key: 'Dashboard', iconName: 'lucide:home', action: () => console.log('Dashboard selected') },
|
|
||||||
{ key: 'Inbox', iconName: 'lucide:inbox', action: () => console.log('Inbox selected') },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Workspace',
|
|
||||||
tabs: [
|
|
||||||
{ key: 'Projects', iconName: 'lucide:folder', action: () => console.log('Projects selected') },
|
|
||||||
{ key: 'Tasks', iconName: 'lucide:checkSquare', action: () => console.log('Tasks selected') },
|
|
||||||
{ key: 'Documents', iconName: 'lucide:fileText', action: () => console.log('Documents selected') },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Analytics',
|
|
||||||
tabs: [
|
|
||||||
{ key: 'Reports', iconName: 'lucide:barChart3', action: () => console.log('Reports selected') },
|
|
||||||
{ key: 'Insights', iconName: 'lucide:lightbulb', action: () => console.log('Insights selected') },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Main menu bottom tabs (pinned to bottom)
|
const appuiElement = document.createElement('dees-appui-base') as DeesAppuiBase;
|
||||||
const mainMenuBottomTabs: ITab[] = [
|
containerElement.appendChild(appuiElement);
|
||||||
{ key: 'Settings', iconName: 'lucide:settings', action: () => console.log('Settings selected') },
|
|
||||||
{ key: 'Help', iconName: 'lucide:helpCircle', action: () => console.log('Help selected') },
|
|
||||||
];
|
|
||||||
|
|
||||||
// Secondary menu groups (second sidebar with collapsible groups)
|
// Initialize after element is connected
|
||||||
// These showcase the new shadcn-style design with badges and collapsible sections
|
setTimeout(async () => {
|
||||||
const secondaryMenuGroups: ISecondaryMenuGroup[] = [
|
await appuiElement.updateComplete;
|
||||||
{
|
|
||||||
name: 'Quick Access',
|
|
||||||
iconName: 'lucide:zap',
|
|
||||||
items: [
|
|
||||||
{ key: 'Overview', iconName: 'layoutDashboard', action: () => console.log('Overview selected') },
|
|
||||||
{ key: 'Recent Activity', iconName: 'clock', action: () => console.log('Recent Activity selected'), badge: 5 },
|
|
||||||
{ key: 'Favorites', iconName: 'star', action: () => console.log('Favorites selected') },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Resources',
|
|
||||||
iconName: 'lucide:layers',
|
|
||||||
items: [
|
|
||||||
{ key: 'Components', iconName: 'package', action: () => console.log('Components selected'), badge: 24 },
|
|
||||||
{ key: 'Services', iconName: 'server', action: () => console.log('Services selected'), badge: 'new', badgeVariant: 'success' },
|
|
||||||
{ key: 'APIs', iconName: 'globe', action: () => console.log('APIs selected'), badge: 3, badgeVariant: 'warning' },
|
|
||||||
{ key: 'Webhooks', iconName: 'webhook', action: () => console.log('Webhooks selected') },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Data Management',
|
|
||||||
iconName: 'lucide:database',
|
|
||||||
items: [
|
|
||||||
{ key: 'Database', iconName: 'database', action: () => console.log('Database selected') },
|
|
||||||
{ key: 'Storage', iconName: 'hardDrive', action: () => console.log('Storage selected'), badge: '85%', badgeVariant: 'warning' },
|
|
||||||
{ key: 'Backups', iconName: 'archive', action: () => console.log('Backups selected'), badge: 'OK', badgeVariant: 'success' },
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'System',
|
|
||||||
iconName: 'lucide:settings',
|
|
||||||
collapsed: true,
|
|
||||||
items: [
|
|
||||||
{ key: 'Configuration', iconName: 'sliders', action: () => console.log('Configuration selected') },
|
|
||||||
{ key: 'Integrations', iconName: 'plug', action: () => console.log('Integrations selected'), badge: 2, badgeVariant: 'error' },
|
|
||||||
{ key: 'Permissions', iconName: 'shield', action: () => console.log('Permissions selected') },
|
|
||||||
{ key: 'Logs', iconName: 'fileText', action: () => console.log('Logs selected') },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
// Main content tabs
|
// Configure using the unified API
|
||||||
const mainContentTabs: ITab[] = [
|
appuiElement.configure(appConfig);
|
||||||
{ key: 'Details', iconName: 'lucide:file', action: () => console.log('Details tab') },
|
|
||||||
{ key: 'Logs', iconName: 'lucide:list', action: () => console.log('Logs tab') },
|
|
||||||
{ key: 'Metrics', iconName: 'lucide:lineChart', action: () => console.log('Metrics tab') },
|
|
||||||
];
|
|
||||||
|
|
||||||
// Profile menu items
|
// Add demo activity entries
|
||||||
const profileMenuItems: (plugins.tsclass.website.IMenuItem & { shortcut?: string } | { divider: true })[] = [
|
setTimeout(() => {
|
||||||
{ name: 'Profile Settings', iconName: 'user', action: async () => console.log('Profile settings') },
|
appuiElement.activityLog.addMany([
|
||||||
{ name: 'Account', iconName: 'settings', action: async () => console.log('Account settings') },
|
{
|
||||||
{ divider: true },
|
type: 'login',
|
||||||
{ name: 'Help & Support', iconName: 'helpCircle', action: async () => console.log('Help') },
|
user: 'Jane Smith',
|
||||||
{ name: 'Keyboard Shortcuts', iconName: 'keyboard', shortcut: 'Cmd+K', action: async () => console.log('Shortcuts') },
|
message: 'logged in from Chrome on macOS'
|
||||||
{ divider: true },
|
},
|
||||||
{ name: 'Sign Out', iconName: 'logOut', action: async () => console.log('Sign out') }
|
{
|
||||||
];
|
type: 'create',
|
||||||
|
user: 'Jane Smith',
|
||||||
|
message: 'created project "Frontend App"'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'update',
|
||||||
|
user: 'John Doe',
|
||||||
|
message: 'updated API documentation'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'view',
|
||||||
|
user: 'Jane Smith',
|
||||||
|
message: 'viewed dashboard analytics'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'delete',
|
||||||
|
user: 'Admin',
|
||||||
|
message: 'removed deprecated endpoint'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'custom',
|
||||||
|
user: 'System',
|
||||||
|
message: 'scheduled backup completed',
|
||||||
|
iconName: 'lucide:database'
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
// Subscribe to view changes
|
||||||
|
appuiElement.viewChanged$.subscribe((event) => {
|
||||||
|
console.log('View changed event:', event);
|
||||||
|
// Update breadcrumbs based on view
|
||||||
|
appuiElement.setBreadcrumbs(event.view.name);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Subscribe to lifecycle events
|
||||||
|
appuiElement.viewLifecycle$.subscribe((event) => {
|
||||||
|
console.log('Lifecycle event:', event.type, event.viewId);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Demo: Dynamically update a badge after 5 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
appuiElement.setMainMenuBadge('tasks', 15);
|
||||||
|
appuiElement.activityLog.add({
|
||||||
|
type: 'update',
|
||||||
|
user: 'System',
|
||||||
|
message: 'new tasks added'
|
||||||
|
});
|
||||||
|
}, 5000);
|
||||||
|
}, 0);
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<dees-demowrapper>
|
<dees-demowrapper>
|
||||||
<style>
|
${containerElement}
|
||||||
${css`
|
|
||||||
.demo-container {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
`}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<div class="demo-container">
|
|
||||||
<dees-appui-base
|
|
||||||
.appbarMenuItems=${menuItems}
|
|
||||||
.appbarBreadcrumbs=${'Dashboard'}
|
|
||||||
.appbarUser=${{
|
|
||||||
name: 'Jane Smith',
|
|
||||||
email: 'jane.smith@example.com',
|
|
||||||
status: 'online' as 'online' | 'offline' | 'busy' | 'away'
|
|
||||||
}}
|
|
||||||
.appbarProfileMenuItems=${profileMenuItems}
|
|
||||||
.appbarShowWindowControls=${true}
|
|
||||||
.appbarShowSearch=${true}
|
|
||||||
.mainmenuLogoIcon=${'lucide:box'}
|
|
||||||
.mainmenuLogoText=${'Acme App'}
|
|
||||||
.mainmenuGroups=${mainMenuGroups}
|
|
||||||
.mainmenuBottomTabs=${mainMenuBottomTabs}
|
|
||||||
.secondarymenuHeading=${'Dashboard'}
|
|
||||||
.secondarymenuGroups=${secondaryMenuGroups}
|
|
||||||
.maincontentTabs=${mainContentTabs}
|
|
||||||
@appbar-menu-select=${(e: CustomEvent) => console.log('Menu selected:', e.detail)}
|
|
||||||
@appbar-breadcrumb-navigate=${(e: CustomEvent) => console.log('Breadcrumb:', e.detail)}
|
|
||||||
@appbar-search-click=${() => console.log('Search clicked')}
|
|
||||||
@appbar-user-menu-open=${() => console.log('User menu opened')}
|
|
||||||
@appbar-profile-menu-select=${(e: CustomEvent) => console.log('Profile menu selected:', e.detail)}
|
|
||||||
@mainmenu-tab-select=${(e: CustomEvent) => console.log('Tab selected:', e.detail)}
|
|
||||||
@secondarymenu-item-select=${(e: CustomEvent) => console.log('Item selected:', e.detail)}
|
|
||||||
>
|
|
||||||
<div slot="maincontent" style="padding: 40px; color: #a3a3a3; font-family: 'Geist Sans', 'Inter', -apple-system, sans-serif;">
|
|
||||||
<h1 style="color: #fafafa; font-weight: 600; font-size: 24px; margin-bottom: 8px;">Welcome to Acme App</h1>
|
|
||||||
<p style="color: #737373; margin-bottom: 32px;">This demo showcases the AppUI component system with the new SecondaryMenu.</p>
|
|
||||||
|
|
||||||
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; margin-bottom: 32px;">
|
|
||||||
<div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 8px; padding: 20px;">
|
|
||||||
<h3 style="color: #fafafa; font-size: 14px; font-weight: 600; margin-bottom: 8px;">SecondaryMenu Features</h3>
|
|
||||||
<ul style="margin: 0; padding-left: 20px; font-size: 13px; line-height: 1.8;">
|
|
||||||
<li>Collapsible groups with smooth animations</li>
|
|
||||||
<li>Badge support (counts, status, variants)</li>
|
|
||||||
<li>Dynamic heading from MainMenu selection</li>
|
|
||||||
<li>shadcn-inspired modern design</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 8px; padding: 20px;">
|
|
||||||
<h3 style="color: #fafafa; font-size: 14px; font-weight: 600; margin-bottom: 8px;">Badge Variants</h3>
|
|
||||||
<div style="display: flex; flex-wrap: wrap; gap: 8px; font-size: 12px;">
|
|
||||||
<span style="background: #27272a; color: #a1a1aa; padding: 2px 8px; border-radius: 9px;">default</span>
|
|
||||||
<span style="background: #14532d; color: #4ade80; padding: 2px 8px; border-radius: 9px;">success</span>
|
|
||||||
<span style="background: #451a03; color: #fbbf24; padding: 2px 8px; border-radius: 9px;">warning</span>
|
|
||||||
<span style="background: #450a0a; color: #f87171; padding: 2px 8px; border-radius: 9px;">error</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p style="font-size: 13px; color: #525252;">
|
|
||||||
Try clicking items in the MainMenu (left) - the SecondaryMenu heading updates automatically.
|
|
||||||
Click group headers in the SecondaryMenu to collapse/expand sections.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</dees-appui-base>
|
|
||||||
</div>
|
|
||||||
</dees-demowrapper>
|
</dees-demowrapper>
|
||||||
`;
|
`;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import {
|
|||||||
cssManager,
|
cssManager,
|
||||||
state,
|
state,
|
||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
|
import * as domtools from '@design.estate/dees-domtools';
|
||||||
import * as interfaces from '../../interfaces/index.js';
|
import * as interfaces from '../../interfaces/index.js';
|
||||||
import * as plugins from '../../00plugins.js';
|
|
||||||
import type { DeesAppuiBar } from '../dees-appui-appbar/index.js';
|
import type { DeesAppuiBar } from '../dees-appui-appbar/index.js';
|
||||||
import type { DeesAppuiMainmenu } from '../dees-appui-mainmenu/dees-appui-mainmenu.js';
|
import type { DeesAppuiMainmenu } from '../dees-appui-mainmenu/dees-appui-mainmenu.js';
|
||||||
import type { DeesAppuiSecondarymenu } from '../dees-appui-secondarymenu/dees-appui-secondarymenu.js';
|
import type { DeesAppuiSecondarymenu } from '../dees-appui-secondarymenu/dees-appui-secondarymenu.js';
|
||||||
@@ -17,6 +17,9 @@ import type { DeesAppuiMaincontent } from '../dees-appui-maincontent/dees-appui-
|
|||||||
import type { DeesAppuiActivitylog } from '../dees-appui-activitylog/dees-appui-activitylog.js';
|
import type { DeesAppuiActivitylog } from '../dees-appui-activitylog/dees-appui-activitylog.js';
|
||||||
import { demoFunc } from './dees-appui-base.demo.js';
|
import { demoFunc } from './dees-appui-base.demo.js';
|
||||||
|
|
||||||
|
// View registry for managing views
|
||||||
|
import { ViewRegistry } from './view.registry.js';
|
||||||
|
|
||||||
// Import child components
|
// Import child components
|
||||||
import '../dees-appui-appbar/index.js';
|
import '../dees-appui-appbar/index.js';
|
||||||
import '../dees-appui-mainmenu/dees-appui-mainmenu.js';
|
import '../dees-appui-mainmenu/dees-appui-mainmenu.js';
|
||||||
@@ -24,10 +27,30 @@ import '../dees-appui-secondarymenu/dees-appui-secondarymenu.js';
|
|||||||
import '../dees-appui-maincontent/dees-appui-maincontent.js';
|
import '../dees-appui-maincontent/dees-appui-maincontent.js';
|
||||||
import '../dees-appui-activitylog/dees-appui-activitylog.js';
|
import '../dees-appui-activitylog/dees-appui-activitylog.js';
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'dees-appui-base': DeesAppuiBase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@customElement('dees-appui-base')
|
@customElement('dees-appui-base')
|
||||||
export class DeesAppuiBase extends DeesElement {
|
export class DeesAppuiBase extends DeesElement {
|
||||||
public static demo = demoFunc;
|
public static demo = demoFunc;
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// REACTIVE OBSERVABLES (RxJS Subjects)
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
/** Observable stream of view lifecycle events */
|
||||||
|
public viewLifecycle$ = new domtools.plugins.smartrx.rxjs.Subject<interfaces.IViewLifecycleEvent>();
|
||||||
|
|
||||||
|
/** Observable stream of view change events */
|
||||||
|
public viewChanged$ = new domtools.plugins.smartrx.rxjs.Subject<interfaces.IViewChangeEvent>();
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// INTERNAL PROPERTIES (Properties for child components)
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
// Properties for appbar
|
// Properties for appbar
|
||||||
@property({ type: Array })
|
@property({ type: Array })
|
||||||
accessor appbarMenuItems: interfaces.IAppBarMenuItem[] = [];
|
accessor appbarMenuItems: interfaces.IAppBarMenuItem[] = [];
|
||||||
@@ -41,17 +64,11 @@ export class DeesAppuiBase extends DeesElement {
|
|||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
accessor appbarShowWindowControls: boolean = true;
|
accessor appbarShowWindowControls: boolean = true;
|
||||||
|
|
||||||
|
|
||||||
@property({ type: Object })
|
@property({ type: Object })
|
||||||
accessor appbarUser: {
|
accessor appbarUser: interfaces.IAppUser | undefined = undefined;
|
||||||
name: string;
|
|
||||||
email?: string;
|
|
||||||
avatar?: string;
|
|
||||||
status?: 'online' | 'offline' | 'busy' | 'away';
|
|
||||||
} | undefined = undefined;
|
|
||||||
|
|
||||||
@property({ type: Array })
|
@property({ type: Array })
|
||||||
accessor appbarProfileMenuItems: (plugins.tsclass.website.IMenuItem & { shortcut?: string } | { divider: true })[] = [];
|
accessor appbarProfileMenuItems: interfaces.IAppBarMenuItem[] = [];
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
accessor appbarShowSearch: boolean = false;
|
accessor appbarShowSearch: boolean = false;
|
||||||
@@ -77,7 +94,7 @@ export class DeesAppuiBase extends DeesElement {
|
|||||||
|
|
||||||
// Properties for secondarymenu
|
// Properties for secondarymenu
|
||||||
@property({ type: String })
|
@property({ type: String })
|
||||||
accessor secondarymenuHeading: string = 'Menu';
|
accessor secondarymenuHeading: string = '';
|
||||||
|
|
||||||
@property({ type: Array })
|
@property({ type: Array })
|
||||||
accessor secondarymenuGroups: interfaces.ISecondaryMenuGroup[] = [];
|
accessor secondarymenuGroups: interfaces.ISecondaryMenuGroup[] = [];
|
||||||
@@ -85,10 +102,6 @@ export class DeesAppuiBase extends DeesElement {
|
|||||||
@property({ type: Object })
|
@property({ type: Object })
|
||||||
accessor secondarymenuSelectedItem: interfaces.ISecondaryMenuItem | undefined = undefined;
|
accessor secondarymenuSelectedItem: interfaces.ISecondaryMenuItem | undefined = undefined;
|
||||||
|
|
||||||
/** Legacy support for flat options (backward compatibility) */
|
|
||||||
@property({ type: Array })
|
|
||||||
accessor secondarymenuOptions: (interfaces.ISelectionOption | { divider: true })[] = [];
|
|
||||||
|
|
||||||
// Collapse states
|
// Collapse states
|
||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
accessor mainmenuCollapsed: boolean = false;
|
accessor mainmenuCollapsed: boolean = false;
|
||||||
@@ -100,6 +113,9 @@ export class DeesAppuiBase extends DeesElement {
|
|||||||
@property({ type: Array })
|
@property({ type: Array })
|
||||||
accessor maincontentTabs: interfaces.ITab[] = [];
|
accessor maincontentTabs: interfaces.ITab[] = [];
|
||||||
|
|
||||||
|
@property({ type: Object })
|
||||||
|
accessor maincontentSelectedTab: interfaces.ITab | undefined = undefined;
|
||||||
|
|
||||||
// References to child components
|
// References to child components
|
||||||
@state()
|
@state()
|
||||||
accessor appbar: DeesAppuiBar | undefined = undefined;
|
accessor appbar: DeesAppuiBar | undefined = undefined;
|
||||||
@@ -114,7 +130,16 @@ export class DeesAppuiBase extends DeesElement {
|
|||||||
accessor maincontent: DeesAppuiMaincontent | undefined = undefined;
|
accessor maincontent: DeesAppuiMaincontent | undefined = undefined;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
accessor activitylog: DeesAppuiActivitylog | undefined = undefined;
|
accessor activitylogElement: DeesAppuiActivitylog | undefined = undefined;
|
||||||
|
|
||||||
|
// Current view state
|
||||||
|
@state()
|
||||||
|
accessor currentView: interfaces.IViewDefinition | undefined = undefined;
|
||||||
|
|
||||||
|
// Internal services
|
||||||
|
private viewRegistry: ViewRegistry = new ViewRegistry();
|
||||||
|
private routerCleanup: (() => void) | null = null;
|
||||||
|
private searchCallback: ((query: string) => void) | null = null;
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
@@ -135,7 +160,7 @@ export class DeesAppuiBase extends DeesElement {
|
|||||||
grid-template-rows: 1fr;
|
grid-template-rows: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Z-index layering for proper stacking (position: relative required for z-index to work) */
|
/* Z-index layering for proper stacking */
|
||||||
.maingrid > dees-appui-mainmenu {
|
.maingrid > dees-appui-mainmenu {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
@@ -155,13 +180,20 @@ export class DeesAppuiBase extends DeesElement {
|
|||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* View container for dynamically loaded views */
|
||||||
|
.view-container {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-container:empty {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|
||||||
// INSTANCE
|
|
||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<style></style>
|
|
||||||
<dees-appui-appbar
|
<dees-appui-appbar
|
||||||
.menuItems=${this.appbarMenuItems}
|
.menuItems=${this.appbarMenuItems}
|
||||||
.breadcrumbs=${this.appbarBreadcrumbs}
|
.breadcrumbs=${this.appbarBreadcrumbs}
|
||||||
@@ -173,6 +205,7 @@ export class DeesAppuiBase extends DeesElement {
|
|||||||
@menu-select=${(e: CustomEvent) => this.handleAppbarMenuSelect(e)}
|
@menu-select=${(e: CustomEvent) => this.handleAppbarMenuSelect(e)}
|
||||||
@breadcrumb-navigate=${(e: CustomEvent) => this.handleAppbarBreadcrumbNavigate(e)}
|
@breadcrumb-navigate=${(e: CustomEvent) => this.handleAppbarBreadcrumbNavigate(e)}
|
||||||
@search-click=${() => this.handleAppbarSearchClick()}
|
@search-click=${() => this.handleAppbarSearchClick()}
|
||||||
|
@search-query=${(e: CustomEvent) => this.handleAppbarSearchQuery(e)}
|
||||||
@user-menu-open=${() => this.handleAppbarUserMenuOpen()}
|
@user-menu-open=${() => this.handleAppbarUserMenuOpen()}
|
||||||
@profile-menu-select=${(e: CustomEvent) => this.handleAppbarProfileMenuSelect(e)}
|
@profile-menu-select=${(e: CustomEvent) => this.handleAppbarProfileMenuSelect(e)}
|
||||||
></dees-appui-appbar>
|
></dees-appui-appbar>
|
||||||
@@ -191,7 +224,6 @@ export class DeesAppuiBase extends DeesElement {
|
|||||||
<dees-appui-secondarymenu
|
<dees-appui-secondarymenu
|
||||||
.heading=${this.secondarymenuHeading}
|
.heading=${this.secondarymenuHeading}
|
||||||
.groups=${this.secondarymenuGroups}
|
.groups=${this.secondarymenuGroups}
|
||||||
.selectionOptions=${this.secondarymenuOptions}
|
|
||||||
.selectedItem=${this.secondarymenuSelectedItem}
|
.selectedItem=${this.secondarymenuSelectedItem}
|
||||||
.collapsed=${this.secondarymenuCollapsed}
|
.collapsed=${this.secondarymenuCollapsed}
|
||||||
@item-select=${(e: CustomEvent) => this.handleSecondarymenuItemSelect(e)}
|
@item-select=${(e: CustomEvent) => this.handleSecondarymenuItemSelect(e)}
|
||||||
@@ -199,7 +231,10 @@ export class DeesAppuiBase extends DeesElement {
|
|||||||
></dees-appui-secondarymenu>
|
></dees-appui-secondarymenu>
|
||||||
<dees-appui-maincontent
|
<dees-appui-maincontent
|
||||||
.tabs=${this.maincontentTabs}
|
.tabs=${this.maincontentTabs}
|
||||||
|
.selectedTab=${this.maincontentSelectedTab}
|
||||||
|
@tab-select=${(e: CustomEvent) => this.handleContentTabSelect(e)}
|
||||||
>
|
>
|
||||||
|
<div class="view-container"></div>
|
||||||
<slot name="maincontent"></slot>
|
<slot name="maincontent"></slot>
|
||||||
</dees-appui-maincontent>
|
</dees-appui-maincontent>
|
||||||
<dees-appui-activitylog></dees-appui-activitylog>
|
<dees-appui-activitylog></dees-appui-activitylog>
|
||||||
@@ -209,14 +244,643 @@ export class DeesAppuiBase extends DeesElement {
|
|||||||
|
|
||||||
async firstUpdated() {
|
async firstUpdated() {
|
||||||
// Get references to child components
|
// Get references to child components
|
||||||
this.appbar = this.shadowRoot.querySelector('dees-appui-appbar');
|
this.appbar = this.shadowRoot!.querySelector('dees-appui-appbar') as DeesAppuiBar;
|
||||||
this.mainmenu = this.shadowRoot.querySelector('dees-appui-mainmenu');
|
this.mainmenu = this.shadowRoot!.querySelector('dees-appui-mainmenu') as DeesAppuiMainmenu;
|
||||||
this.secondarymenu = this.shadowRoot.querySelector('dees-appui-secondarymenu');
|
this.secondarymenu = this.shadowRoot!.querySelector('dees-appui-secondarymenu') as DeesAppuiSecondarymenu;
|
||||||
this.maincontent = this.shadowRoot.querySelector('dees-appui-maincontent');
|
this.maincontent = this.shadowRoot!.querySelector('dees-appui-maincontent') as DeesAppuiMaincontent;
|
||||||
this.activitylog = this.shadowRoot.querySelector('dees-appui-activitylog');
|
this.activitylogElement = this.shadowRoot!.querySelector('dees-appui-activitylog') as DeesAppuiActivitylog;
|
||||||
|
|
||||||
|
// Set appui reference in view registry for lifecycle context
|
||||||
|
this.viewRegistry.setAppuiRef(this as unknown as interfaces.TDeesAppuiBase);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event handlers for appbar
|
async disconnectedCallback() {
|
||||||
|
await super.disconnectedCallback();
|
||||||
|
// Clean up router listener
|
||||||
|
if (this.routerCleanup) {
|
||||||
|
this.routerCleanup();
|
||||||
|
this.routerCleanup = null;
|
||||||
|
}
|
||||||
|
// Complete subjects
|
||||||
|
this.viewLifecycle$.complete();
|
||||||
|
this.viewChanged$.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// PROGRAMMATIC API: APP BAR
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the app bar menu items (File, Edit, View, etc.)
|
||||||
|
*/
|
||||||
|
public setAppBarMenus(menus: interfaces.IAppBarMenuItem[]): void {
|
||||||
|
this.appbarMenuItems = [...menus];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a single app bar menu by name
|
||||||
|
*/
|
||||||
|
public updateAppBarMenu(name: string, update: Partial<interfaces.IAppBarMenuItem>): void {
|
||||||
|
this.appbarMenuItems = this.appbarMenuItems.map(menu => {
|
||||||
|
// Check if it's not a divider and has a name property
|
||||||
|
if ('name' in menu && menu.name === name) {
|
||||||
|
return { ...menu, ...update };
|
||||||
|
}
|
||||||
|
return menu;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the breadcrumbs (string or array)
|
||||||
|
*/
|
||||||
|
public setBreadcrumbs(breadcrumbs: string | string[]): void {
|
||||||
|
if (Array.isArray(breadcrumbs)) {
|
||||||
|
this.appbarBreadcrumbs = breadcrumbs.join(this.appbarBreadcrumbSeparator);
|
||||||
|
} else {
|
||||||
|
this.appbarBreadcrumbs = breadcrumbs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current user
|
||||||
|
*/
|
||||||
|
public setUser(user: interfaces.IAppUser | undefined): void {
|
||||||
|
this.appbarUser = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the profile dropdown menu items
|
||||||
|
*/
|
||||||
|
public setProfileMenuItems(items: interfaces.IAppBarMenuItem[]): void {
|
||||||
|
this.appbarProfileMenuItems = [...items];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set search bar visibility
|
||||||
|
*/
|
||||||
|
public setSearchVisible(visible: boolean): void {
|
||||||
|
this.appbarShowSearch = visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set window controls visibility
|
||||||
|
*/
|
||||||
|
public setWindowControlsVisible(visible: boolean): void {
|
||||||
|
this.appbarShowWindowControls = visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a search callback
|
||||||
|
*/
|
||||||
|
public onSearch(callback: (query: string) => void): void {
|
||||||
|
this.searchCallback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// PROGRAMMATIC API: MAIN MENU
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the entire main menu configuration
|
||||||
|
*/
|
||||||
|
public setMainMenu(config: interfaces.IMainMenuConfig): void {
|
||||||
|
if (config.logoIcon !== undefined) {
|
||||||
|
this.mainmenuLogoIcon = config.logoIcon;
|
||||||
|
}
|
||||||
|
if (config.logoText !== undefined) {
|
||||||
|
this.mainmenuLogoText = config.logoText;
|
||||||
|
}
|
||||||
|
if (config.groups !== undefined) {
|
||||||
|
this.mainmenuGroups = [...config.groups];
|
||||||
|
}
|
||||||
|
if (config.bottomTabs !== undefined) {
|
||||||
|
this.mainmenuBottomTabs = [...config.bottomTabs];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a specific menu group by name
|
||||||
|
*/
|
||||||
|
public updateMainMenuGroup(groupName: string, update: Partial<interfaces.IMenuGroup>): void {
|
||||||
|
this.mainmenuGroups = this.mainmenuGroups.map(group =>
|
||||||
|
group.name === groupName ? { ...group, ...update } : group
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a menu item to a specific group
|
||||||
|
*/
|
||||||
|
public addMainMenuItem(groupName: string, tab: interfaces.ITab): void {
|
||||||
|
this.mainmenuGroups = this.mainmenuGroups.map(group => {
|
||||||
|
if (group.name === groupName) {
|
||||||
|
return {
|
||||||
|
...group,
|
||||||
|
tabs: [...(group.tabs || []), tab],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return group;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a menu item from a group by key
|
||||||
|
*/
|
||||||
|
public removeMainMenuItem(groupName: string, tabKey: string): void {
|
||||||
|
this.mainmenuGroups = this.mainmenuGroups.map(group => {
|
||||||
|
if (group.name === groupName) {
|
||||||
|
return {
|
||||||
|
...group,
|
||||||
|
tabs: (group.tabs || []).filter(t => t.key !== tabKey),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return group;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the selected main menu item by key
|
||||||
|
*/
|
||||||
|
public setMainMenuSelection(tabKey: string): void {
|
||||||
|
for (const group of this.mainmenuGroups) {
|
||||||
|
const tab = group.tabs?.find(t => t.key === tabKey);
|
||||||
|
if (tab) {
|
||||||
|
this.mainmenuSelectedTab = tab;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check bottom tabs
|
||||||
|
const bottomTab = this.mainmenuBottomTabs.find(t => t.key === tabKey);
|
||||||
|
if (bottomTab) {
|
||||||
|
this.mainmenuSelectedTab = bottomTab;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set main menu collapsed state
|
||||||
|
*/
|
||||||
|
public setMainMenuCollapsed(collapsed: boolean): void {
|
||||||
|
this.mainmenuCollapsed = collapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a badge on a main menu item
|
||||||
|
*/
|
||||||
|
public setMainMenuBadge(tabKey: string, badge: string | number): void {
|
||||||
|
this.mainmenuGroups = this.mainmenuGroups.map(group => ({
|
||||||
|
...group,
|
||||||
|
tabs: (group.tabs || []).map(tab =>
|
||||||
|
tab.key === tabKey ? { ...tab, badge } : tab
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
// Also check bottom tabs
|
||||||
|
this.mainmenuBottomTabs = this.mainmenuBottomTabs.map(tab =>
|
||||||
|
tab.key === tabKey ? { ...tab, badge } : tab
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear a badge from a main menu item
|
||||||
|
*/
|
||||||
|
public clearMainMenuBadge(tabKey: string): void {
|
||||||
|
this.mainmenuGroups = this.mainmenuGroups.map(group => ({
|
||||||
|
...group,
|
||||||
|
tabs: (group.tabs || []).map(tab => {
|
||||||
|
if (tab.key === tabKey) {
|
||||||
|
const { badge, ...rest } = tab;
|
||||||
|
return rest;
|
||||||
|
}
|
||||||
|
return tab;
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
// Also check bottom tabs
|
||||||
|
this.mainmenuBottomTabs = this.mainmenuBottomTabs.map(tab => {
|
||||||
|
if (tab.key === tabKey) {
|
||||||
|
const { badge, ...rest } = tab;
|
||||||
|
return rest;
|
||||||
|
}
|
||||||
|
return tab;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// PROGRAMMATIC API: SECONDARY MENU
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the secondary menu configuration
|
||||||
|
*/
|
||||||
|
public setSecondaryMenu(config: { heading?: string; groups: interfaces.ISecondaryMenuGroup[] }): void {
|
||||||
|
if (config.heading !== undefined) {
|
||||||
|
this.secondarymenuHeading = config.heading;
|
||||||
|
}
|
||||||
|
this.secondarymenuGroups = [...config.groups];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a specific secondary menu group
|
||||||
|
*/
|
||||||
|
public updateSecondaryMenuGroup(groupName: string, update: Partial<interfaces.ISecondaryMenuGroup>): void {
|
||||||
|
this.secondarymenuGroups = this.secondarymenuGroups.map(group =>
|
||||||
|
group.name === groupName ? { ...group, ...update } : group
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an item to a secondary menu group
|
||||||
|
*/
|
||||||
|
public addSecondaryMenuItem(
|
||||||
|
groupName: string,
|
||||||
|
item: interfaces.ISecondaryMenuGroup['items'][0]
|
||||||
|
): void {
|
||||||
|
this.secondarymenuGroups = this.secondarymenuGroups.map(group => {
|
||||||
|
if (group.name === groupName) {
|
||||||
|
return {
|
||||||
|
...group,
|
||||||
|
items: [...group.items, item],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return group;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the selected secondary menu item by key
|
||||||
|
*/
|
||||||
|
public setSecondaryMenuSelection(itemKey: string): void {
|
||||||
|
for (const group of this.secondarymenuGroups) {
|
||||||
|
const item = group.items.find(i => i.key === itemKey);
|
||||||
|
if (item) {
|
||||||
|
this.secondarymenuSelectedItem = item;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the secondary menu
|
||||||
|
*/
|
||||||
|
public clearSecondaryMenu(): void {
|
||||||
|
this.secondarymenuHeading = '';
|
||||||
|
this.secondarymenuGroups = [];
|
||||||
|
this.secondarymenuSelectedItem = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// PROGRAMMATIC API: CONTENT TABS
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the content tabs
|
||||||
|
*/
|
||||||
|
public setContentTabs(tabs: interfaces.ITab[]): void {
|
||||||
|
this.maincontentTabs = [...tabs];
|
||||||
|
if (tabs.length > 0 && !this.maincontentSelectedTab) {
|
||||||
|
this.maincontentSelectedTab = tabs[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a content tab
|
||||||
|
*/
|
||||||
|
public addContentTab(tab: interfaces.ITab): void {
|
||||||
|
this.maincontentTabs = [...this.maincontentTabs, tab];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a content tab by key
|
||||||
|
*/
|
||||||
|
public removeContentTab(tabKey: string): void {
|
||||||
|
this.maincontentTabs = this.maincontentTabs.filter(t => t.key !== tabKey);
|
||||||
|
if (this.maincontentSelectedTab?.key === tabKey) {
|
||||||
|
this.maincontentSelectedTab = this.maincontentTabs[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Select a content tab by key
|
||||||
|
*/
|
||||||
|
public selectContentTab(tabKey: string): void {
|
||||||
|
const tab = this.maincontentTabs.find(t => t.key === tabKey);
|
||||||
|
if (tab) {
|
||||||
|
this.maincontentSelectedTab = tab;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the currently selected content tab
|
||||||
|
*/
|
||||||
|
public getSelectedContentTab(): interfaces.ITab | undefined {
|
||||||
|
return this.maincontentSelectedTab;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// PROGRAMMATIC API: ACTIVITY LOG
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the activity log API
|
||||||
|
*/
|
||||||
|
public get activityLog(): interfaces.IActivityLogAPI {
|
||||||
|
if (!this.activitylogElement) {
|
||||||
|
// Return a deferred API that will work after firstUpdated
|
||||||
|
return {
|
||||||
|
add: (entry) => {
|
||||||
|
this.updateComplete.then(() => this.activitylogElement?.add(entry));
|
||||||
|
},
|
||||||
|
addMany: (entries) => {
|
||||||
|
this.updateComplete.then(() => this.activitylogElement?.addMany(entries));
|
||||||
|
},
|
||||||
|
clear: () => {
|
||||||
|
this.updateComplete.then(() => this.activitylogElement?.clear());
|
||||||
|
},
|
||||||
|
getEntries: () => this.activitylogElement?.getEntries() || [],
|
||||||
|
filter: (criteria) => this.activitylogElement?.filter(criteria) || [],
|
||||||
|
search: (query) => this.activitylogElement?.search(query) || [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
add: (entry) => this.activitylogElement!.add(entry),
|
||||||
|
addMany: (entries) => this.activitylogElement!.addMany(entries),
|
||||||
|
clear: () => this.activitylogElement!.clear(),
|
||||||
|
getEntries: () => this.activitylogElement!.getEntries(),
|
||||||
|
filter: (criteria) => this.activitylogElement!.filter(criteria),
|
||||||
|
search: (query) => this.activitylogElement!.search(query),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// PROGRAMMATIC API: NAVIGATION
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to a view by ID
|
||||||
|
*/
|
||||||
|
public async navigateToView(viewId: string, params?: Record<string, string>): Promise<boolean> {
|
||||||
|
const view = this.viewRegistry.get(viewId);
|
||||||
|
if (!view) {
|
||||||
|
console.warn(`Cannot navigate to unknown view: ${viewId}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if current view allows navigation
|
||||||
|
const canLeave = await this.viewRegistry.canLeaveCurrentView();
|
||||||
|
if (canLeave !== true) {
|
||||||
|
if (typeof canLeave === 'string') {
|
||||||
|
// Show confirmation dialog
|
||||||
|
const confirmed = window.confirm(canLeave);
|
||||||
|
if (!confirmed) return false;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit loading event
|
||||||
|
this.viewLifecycle$.next({ type: 'loading', viewId });
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.loadView(view, params);
|
||||||
|
|
||||||
|
// Update URL hash
|
||||||
|
const route = view.route || viewId;
|
||||||
|
const newHash = `#${route}`;
|
||||||
|
if (window.location.hash !== newHash) {
|
||||||
|
window.history.pushState({ viewId }, '', newHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
this.viewLifecycle$.next({ type: 'loadError', viewId, error });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current view
|
||||||
|
*/
|
||||||
|
public getCurrentView(): interfaces.IViewDefinition | undefined {
|
||||||
|
return this.currentView;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get access to the view registry (for advanced use)
|
||||||
|
*/
|
||||||
|
public getViewRegistry(): ViewRegistry {
|
||||||
|
return this.viewRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// UNIFIED CONFIGURATION
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the app shell with a unified config object
|
||||||
|
*/
|
||||||
|
public configure(config: interfaces.IAppConfig): void {
|
||||||
|
// Register views
|
||||||
|
if (config.views) {
|
||||||
|
this.viewRegistry.clear();
|
||||||
|
this.viewRegistry.registerAll(config.views);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply branding
|
||||||
|
if (config.branding) {
|
||||||
|
this.mainmenuLogoIcon = config.branding.logoIcon || '';
|
||||||
|
this.mainmenuLogoText = config.branding.logoText || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply app bar config
|
||||||
|
if (config.appBar) {
|
||||||
|
this.appbarMenuItems = config.appBar.menuItems || [];
|
||||||
|
this.appbarBreadcrumbs = config.appBar.breadcrumbs || '';
|
||||||
|
this.appbarBreadcrumbSeparator = config.appBar.breadcrumbSeparator || ' > ';
|
||||||
|
this.appbarShowWindowControls = config.appBar.showWindowControls ?? true;
|
||||||
|
this.appbarShowSearch = config.appBar.showSearch ?? false;
|
||||||
|
this.appbarUser = config.appBar.user;
|
||||||
|
this.appbarProfileMenuItems = config.appBar.profileMenuItems || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build main menu from view references or direct config
|
||||||
|
if (config.mainMenu) {
|
||||||
|
if (config.mainMenu.sections) {
|
||||||
|
this.mainmenuGroups = this.buildMainMenuFromSections(config);
|
||||||
|
} else if (config.mainMenu.groups) {
|
||||||
|
this.mainmenuGroups = config.mainMenu.groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.mainMenu.logoIcon) {
|
||||||
|
this.mainmenuLogoIcon = config.mainMenu.logoIcon;
|
||||||
|
}
|
||||||
|
if (config.mainMenu.logoText) {
|
||||||
|
this.mainmenuLogoText = config.mainMenu.logoText;
|
||||||
|
}
|
||||||
|
if (config.mainMenu.bottomTabs) {
|
||||||
|
this.mainmenuBottomTabs = config.mainMenu.bottomTabs;
|
||||||
|
} else if (config.mainMenu.bottomItems) {
|
||||||
|
this.mainmenuBottomTabs = this.buildBottomTabsFromItems(config.mainMenu.bottomItems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup domtools.router integration
|
||||||
|
this.setupRouterIntegration(config);
|
||||||
|
|
||||||
|
// Bind event callbacks
|
||||||
|
if (config.onViewChange) {
|
||||||
|
this.viewChanged$.subscribe((event) => {
|
||||||
|
config.onViewChange!(event.viewId, event.view);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.onSearch) {
|
||||||
|
this.searchCallback = config.onSearch;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigate to default view
|
||||||
|
if (config.defaultView) {
|
||||||
|
this.navigateToView(config.defaultView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// PRIVATE HELPER METHODS
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
|
private setupRouterIntegration(config: interfaces.IAppConfig): void {
|
||||||
|
// Handle hash change events
|
||||||
|
const handleHashChange = () => {
|
||||||
|
const hash = window.location.hash.slice(1); // Remove #
|
||||||
|
if (!hash) return;
|
||||||
|
|
||||||
|
const match = this.viewRegistry.findByRoute(hash);
|
||||||
|
if (match) {
|
||||||
|
this.navigateToView(match.view.id, match.params);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('hashchange', handleHashChange);
|
||||||
|
|
||||||
|
// Store cleanup function
|
||||||
|
this.routerCleanup = () => {
|
||||||
|
window.removeEventListener('hashchange', handleHashChange);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle initial route from hash
|
||||||
|
const currentHash = window.location.hash.slice(1);
|
||||||
|
if (currentHash) {
|
||||||
|
const match = this.viewRegistry.findByRoute(currentHash);
|
||||||
|
if (match) {
|
||||||
|
// Use setTimeout to allow component to fully initialize
|
||||||
|
setTimeout(() => this.navigateToView(match.view.id, match.params), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildMainMenuFromSections(config: interfaces.IAppConfig): interfaces.IMenuGroup[] {
|
||||||
|
if (!config.mainMenu?.sections) return [];
|
||||||
|
|
||||||
|
return config.mainMenu.sections.map((section) => ({
|
||||||
|
name: section.name,
|
||||||
|
tabs: section.views
|
||||||
|
.map((viewId) => {
|
||||||
|
const view = this.viewRegistry.get(viewId);
|
||||||
|
if (!view) {
|
||||||
|
console.warn(`View "${viewId}" not found in registry`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
key: view.id,
|
||||||
|
iconName: view.iconName,
|
||||||
|
action: () => this.navigateToView(viewId),
|
||||||
|
badge: view.badge,
|
||||||
|
} as interfaces.ITab;
|
||||||
|
})
|
||||||
|
.filter(Boolean) as interfaces.ITab[],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildBottomTabsFromItems(items: string[]): interfaces.ITab[] {
|
||||||
|
return items
|
||||||
|
.map((viewId) => {
|
||||||
|
const view = this.viewRegistry.get(viewId);
|
||||||
|
if (!view) {
|
||||||
|
console.warn(`View "${viewId}" not found in registry`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
key: view.id,
|
||||||
|
iconName: view.iconName,
|
||||||
|
action: () => this.navigateToView(viewId),
|
||||||
|
} as interfaces.ITab;
|
||||||
|
})
|
||||||
|
.filter(Boolean) as interfaces.ITab[];
|
||||||
|
}
|
||||||
|
|
||||||
|
private async loadView(
|
||||||
|
view: interfaces.IViewDefinition,
|
||||||
|
params?: Record<string, string>
|
||||||
|
): Promise<void> {
|
||||||
|
const previousView = this.currentView;
|
||||||
|
this.currentView = view;
|
||||||
|
|
||||||
|
// Get view container
|
||||||
|
const viewContainer = this.maincontent?.querySelector('.view-container')
|
||||||
|
|| this.shadowRoot?.querySelector('.view-container');
|
||||||
|
|
||||||
|
if (viewContainer) {
|
||||||
|
// Activate view with caching and lifecycle hooks
|
||||||
|
const element = await this.viewRegistry.activateView(
|
||||||
|
view.id,
|
||||||
|
viewContainer as HTMLElement,
|
||||||
|
params
|
||||||
|
);
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
// Emit lifecycle event
|
||||||
|
this.viewLifecycle$.next({
|
||||||
|
type: 'activated',
|
||||||
|
viewId: view.id,
|
||||||
|
element,
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply view-specific secondary menu
|
||||||
|
if (view.secondaryMenu) {
|
||||||
|
this.secondarymenuGroups = view.secondaryMenu;
|
||||||
|
this.secondarymenuHeading = view.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply view-specific content tabs
|
||||||
|
if (view.contentTabs) {
|
||||||
|
this.maincontentTabs = view.contentTabs;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update main menu selection
|
||||||
|
this.setMainMenuSelection(view.id);
|
||||||
|
|
||||||
|
// Emit view change event
|
||||||
|
const changeEvent: interfaces.IViewChangeEvent = {
|
||||||
|
viewId: view.id,
|
||||||
|
view,
|
||||||
|
previousView,
|
||||||
|
params,
|
||||||
|
};
|
||||||
|
this.viewChanged$.next(changeEvent);
|
||||||
|
|
||||||
|
// Also dispatch DOM event for backwards compatibility
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('view-change', {
|
||||||
|
detail: changeEvent,
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// EVENT HANDLERS (Internal)
|
||||||
|
// ==========================================
|
||||||
|
|
||||||
private handleAppbarMenuSelect(e: CustomEvent) {
|
private handleAppbarMenuSelect(e: CustomEvent) {
|
||||||
this.dispatchEvent(new CustomEvent('appbar-menu-select', {
|
this.dispatchEvent(new CustomEvent('appbar-menu-select', {
|
||||||
detail: e.detail,
|
detail: e.detail,
|
||||||
@@ -240,6 +904,17 @@ export class DeesAppuiBase extends DeesElement {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleAppbarSearchQuery(e: CustomEvent) {
|
||||||
|
if (this.searchCallback) {
|
||||||
|
this.searchCallback(e.detail.query);
|
||||||
|
}
|
||||||
|
this.dispatchEvent(new CustomEvent('search-query', {
|
||||||
|
detail: e.detail,
|
||||||
|
bubbles: true,
|
||||||
|
composed: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
private handleAppbarUserMenuOpen() {
|
private handleAppbarUserMenuOpen() {
|
||||||
this.dispatchEvent(new CustomEvent('appbar-user-menu-open', {
|
this.dispatchEvent(new CustomEvent('appbar-user-menu-open', {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
@@ -255,11 +930,8 @@ export class DeesAppuiBase extends DeesElement {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event handlers for mainmenu
|
|
||||||
private handleMainmenuTabSelect(e: CustomEvent) {
|
private handleMainmenuTabSelect(e: CustomEvent) {
|
||||||
this.mainmenuSelectedTab = e.detail.tab;
|
this.mainmenuSelectedTab = e.detail.tab;
|
||||||
// Update secondary menu heading based on main menu selection
|
|
||||||
this.secondarymenuHeading = e.detail.tab.key;
|
|
||||||
this.dispatchEvent(new CustomEvent('mainmenu-tab-select', {
|
this.dispatchEvent(new CustomEvent('mainmenu-tab-select', {
|
||||||
detail: e.detail,
|
detail: e.detail,
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
@@ -267,7 +939,6 @@ export class DeesAppuiBase extends DeesElement {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event handlers for secondarymenu
|
|
||||||
private handleSecondarymenuItemSelect(e: CustomEvent) {
|
private handleSecondarymenuItemSelect(e: CustomEvent) {
|
||||||
this.secondarymenuSelectedItem = e.detail.item;
|
this.secondarymenuSelectedItem = e.detail.item;
|
||||||
this.dispatchEvent(new CustomEvent('secondarymenu-item-select', {
|
this.dispatchEvent(new CustomEvent('secondarymenu-item-select', {
|
||||||
@@ -277,7 +948,6 @@ export class DeesAppuiBase extends DeesElement {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event handlers for collapse state changes
|
|
||||||
private handleMainmenuCollapseChange(e: CustomEvent) {
|
private handleMainmenuCollapseChange(e: CustomEvent) {
|
||||||
this.mainmenuCollapsed = e.detail.collapsed;
|
this.mainmenuCollapsed = e.detail.collapsed;
|
||||||
this.dispatchEvent(new CustomEvent('mainmenu-collapse-change', {
|
this.dispatchEvent(new CustomEvent('mainmenu-collapse-change', {
|
||||||
@@ -295,4 +965,13 @@ export class DeesAppuiBase extends DeesElement {
|
|||||||
composed: true
|
composed: true
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private handleContentTabSelect(e: CustomEvent) {
|
||||||
|
this.maincontentSelectedTab = e.detail.tab;
|
||||||
|
this.dispatchEvent(new CustomEvent('content-tab-select', {
|
||||||
|
detail: e.detail,
|
||||||
|
bubbles: true,
|
||||||
|
composed: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
export * from './dees-appui-base.js';
|
export * from './dees-appui-base.js';
|
||||||
|
export * from './view.registry.js';
|
||||||
|
|||||||
560
ts_web/elements/00group-appui/dees-appui-base/readme.md
Normal file
560
ts_web/elements/00group-appui/dees-appui-base/readme.md
Normal file
@@ -0,0 +1,560 @@
|
|||||||
|
# DeesAppuiBase
|
||||||
|
|
||||||
|
A comprehensive application shell component providing a complete UI framework with navigation, menus, activity logging, and view management.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { html, DeesElement, customElement } from '@design.estate/dees-element';
|
||||||
|
import { DeesAppuiBase } from '@design.estate/dees-catalog';
|
||||||
|
|
||||||
|
@customElement('my-app')
|
||||||
|
class MyApp extends DeesElement {
|
||||||
|
private appui: DeesAppuiBase;
|
||||||
|
|
||||||
|
async firstUpdated() {
|
||||||
|
this.appui = this.shadowRoot.querySelector('dees-appui-base');
|
||||||
|
|
||||||
|
// Configure with views and menu
|
||||||
|
this.appui.configure({
|
||||||
|
branding: { logoIcon: 'lucide:box', logoText: 'My App' },
|
||||||
|
views: [
|
||||||
|
{ id: 'dashboard', name: 'Dashboard', iconName: 'lucide:home', content: 'my-dashboard' },
|
||||||
|
{ id: 'settings', name: 'Settings', iconName: 'lucide:settings', content: 'my-settings' },
|
||||||
|
],
|
||||||
|
mainMenu: {
|
||||||
|
sections: [{ name: 'Main', views: ['dashboard', 'settings'] }]
|
||||||
|
},
|
||||||
|
defaultView: 'dashboard'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`<dees-appui-base></dees-appui-base>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration API
|
||||||
|
|
||||||
|
### `configure(config: IAppConfig)`
|
||||||
|
|
||||||
|
Configure the entire application shell with a single configuration object.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface IAppConfig {
|
||||||
|
branding?: IBrandingConfig;
|
||||||
|
appBar?: IAppBarConfig;
|
||||||
|
views: IViewDefinition[];
|
||||||
|
mainMenu?: IMainMenuConfig;
|
||||||
|
defaultView?: string;
|
||||||
|
activityLog?: IActivityLogConfig;
|
||||||
|
onViewChange?: (viewId: string, view: IViewDefinition) => void;
|
||||||
|
onSearch?: (query: string) => void;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### View Definition
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface IViewDefinition {
|
||||||
|
id: string; // Unique identifier
|
||||||
|
name: string; // Display name
|
||||||
|
iconName?: string; // Icon (e.g., 'lucide:home')
|
||||||
|
content: // View content
|
||||||
|
| string // Tag name ('my-component')
|
||||||
|
| (new () => HTMLElement) // Class constructor
|
||||||
|
| (() => TemplateResult) // Template function
|
||||||
|
| (() => Promise<...>); // Async for lazy loading
|
||||||
|
secondaryMenu?: ISecondaryMenuGroup[];
|
||||||
|
contentTabs?: ITab[];
|
||||||
|
route?: string; // URL route (default: id)
|
||||||
|
badge?: string | number;
|
||||||
|
cache?: boolean; // Cache view instance (default: true)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Programmatic APIs
|
||||||
|
|
||||||
|
### App Bar API
|
||||||
|
|
||||||
|
Control the top application bar.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Set menu items (File, Edit, View, etc.)
|
||||||
|
appui.setAppBarMenus([
|
||||||
|
{
|
||||||
|
name: 'File',
|
||||||
|
submenu: [
|
||||||
|
{ name: 'New', shortcut: 'Cmd+N', action: () => {} },
|
||||||
|
{ name: 'Save', shortcut: 'Cmd+S', action: () => {} },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Update single menu
|
||||||
|
appui.updateAppBarMenu('File', { submenu: [...newItems] });
|
||||||
|
|
||||||
|
// Breadcrumbs
|
||||||
|
appui.setBreadcrumbs('Dashboard > Settings > Profile');
|
||||||
|
appui.setBreadcrumbs(['Dashboard', 'Settings', 'Profile']);
|
||||||
|
|
||||||
|
// User profile
|
||||||
|
appui.setUser({
|
||||||
|
name: 'John Doe',
|
||||||
|
email: 'john@example.com',
|
||||||
|
avatar: '/avatars/john.png',
|
||||||
|
status: 'online' // 'online' | 'offline' | 'busy' | 'away'
|
||||||
|
});
|
||||||
|
|
||||||
|
appui.setProfileMenuItems([
|
||||||
|
{ name: 'Profile', iconName: 'lucide:user', action: () => {} },
|
||||||
|
{ divider: true },
|
||||||
|
{ name: 'Sign Out', iconName: 'lucide:log-out', action: () => {} }
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Search
|
||||||
|
appui.setSearchVisible(true);
|
||||||
|
appui.onSearch((query) => console.log('Search:', query));
|
||||||
|
|
||||||
|
// Window controls (for Electron/Tauri apps)
|
||||||
|
appui.setWindowControlsVisible(false);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Main Menu API (Left Sidebar)
|
||||||
|
|
||||||
|
Control the main navigation menu.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Set entire menu
|
||||||
|
appui.setMainMenu({
|
||||||
|
logoIcon: 'lucide:box',
|
||||||
|
logoText: 'My App',
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
name: 'Main',
|
||||||
|
tabs: [
|
||||||
|
{ key: 'dashboard', iconName: 'lucide:home', action: () => {} },
|
||||||
|
{ key: 'inbox', iconName: 'lucide:inbox', badge: 5, action: () => {} },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
bottomTabs: [
|
||||||
|
{ key: 'settings', iconName: 'lucide:settings', action: () => {} }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update specific group
|
||||||
|
appui.updateMainMenuGroup('Main', { tabs: [...newTabs] });
|
||||||
|
|
||||||
|
// Add/remove items
|
||||||
|
appui.addMainMenuItem('Main', { key: 'tasks', iconName: 'lucide:check', action: () => {} });
|
||||||
|
appui.removeMainMenuItem('Main', 'tasks');
|
||||||
|
|
||||||
|
// Selection
|
||||||
|
appui.setMainMenuSelection('dashboard');
|
||||||
|
appui.setMainMenuCollapsed(true);
|
||||||
|
|
||||||
|
// Badges
|
||||||
|
appui.setMainMenuBadge('inbox', 12);
|
||||||
|
appui.clearMainMenuBadge('inbox');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Secondary Menu API
|
||||||
|
|
||||||
|
Views can control the secondary (contextual) menu.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Set menu
|
||||||
|
appui.setSecondaryMenu({
|
||||||
|
heading: 'Settings',
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
name: 'Account',
|
||||||
|
items: [
|
||||||
|
{ key: 'profile', iconName: 'lucide:user', action: () => {} },
|
||||||
|
{ key: 'security', iconName: 'lucide:shield', action: () => {} },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update group
|
||||||
|
appui.updateSecondaryMenuGroup('Account', { items: newItems });
|
||||||
|
|
||||||
|
// Add item
|
||||||
|
appui.addSecondaryMenuItem('Account', {
|
||||||
|
key: 'notifications',
|
||||||
|
iconName: 'lucide:bell',
|
||||||
|
action: () => {}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Selection
|
||||||
|
appui.setSecondaryMenuSelection('profile');
|
||||||
|
|
||||||
|
// Clear
|
||||||
|
appui.clearSecondaryMenu();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Content Tabs API
|
||||||
|
|
||||||
|
Control tabs in the main content area.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Set tabs
|
||||||
|
appui.setContentTabs([
|
||||||
|
{ key: 'code', iconName: 'lucide:code', action: () => {} },
|
||||||
|
{ key: 'preview', iconName: 'lucide:eye', action: () => {} }
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Add/remove
|
||||||
|
appui.addContentTab({ key: 'debug', iconName: 'lucide:bug', action: () => {} });
|
||||||
|
appui.removeContentTab('debug');
|
||||||
|
|
||||||
|
// Select
|
||||||
|
appui.selectContentTab('preview');
|
||||||
|
|
||||||
|
// Get current
|
||||||
|
const current = appui.getSelectedContentTab();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Activity Log API
|
||||||
|
|
||||||
|
Add activity entries to the right-side activity log.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Add single entry
|
||||||
|
appui.activityLog.add({
|
||||||
|
type: 'create', // 'login' | 'logout' | 'view' | 'create' | 'update' | 'delete' | 'custom'
|
||||||
|
user: 'John Doe',
|
||||||
|
message: 'created a new invoice',
|
||||||
|
iconName: 'lucide:file-plus', // Optional custom icon
|
||||||
|
data: { invoiceId: '123' } // Optional metadata
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add multiple
|
||||||
|
appui.activityLog.addMany([...entries]);
|
||||||
|
|
||||||
|
// Clear
|
||||||
|
appui.activityLog.clear();
|
||||||
|
|
||||||
|
// Query
|
||||||
|
const entries = appui.activityLog.getEntries();
|
||||||
|
const filtered = appui.activityLog.filter({ user: 'John', type: 'create' });
|
||||||
|
const searched = appui.activityLog.search('invoice');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Navigation API
|
||||||
|
|
||||||
|
Navigate between views programmatically.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Navigate to view
|
||||||
|
await appui.navigateToView('settings');
|
||||||
|
await appui.navigateToView('settings', { section: 'profile' });
|
||||||
|
|
||||||
|
// Get current view
|
||||||
|
const current = appui.getCurrentView();
|
||||||
|
|
||||||
|
// Subscribe to view changes
|
||||||
|
appui.viewChanged$.subscribe((event) => {
|
||||||
|
console.log(`Navigated to: ${event.viewId}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Subscribe to lifecycle events
|
||||||
|
appui.viewLifecycle$.subscribe((event) => {
|
||||||
|
if (event.type === 'activated') {
|
||||||
|
console.log(`View ${event.viewId} activated`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## View Lifecycle Hooks
|
||||||
|
|
||||||
|
Views can implement lifecycle hooks to respond to activation/deactivation.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { DeesElement, customElement } from '@design.estate/dees-element';
|
||||||
|
import type { IViewActivationContext, IViewLifecycle } from '@design.estate/dees-catalog';
|
||||||
|
|
||||||
|
@customElement('my-settings-view')
|
||||||
|
class MySettingsView extends DeesElement implements IViewLifecycle {
|
||||||
|
/**
|
||||||
|
* Called when view is activated (displayed)
|
||||||
|
* Receives typed context with appui reference
|
||||||
|
*/
|
||||||
|
async onActivate(context: IViewActivationContext) {
|
||||||
|
const { appui, viewId, params } = context;
|
||||||
|
|
||||||
|
// Set view-specific secondary menu
|
||||||
|
appui.setSecondaryMenu({
|
||||||
|
heading: 'Settings',
|
||||||
|
groups: [{ name: 'Options', items: [...] }]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set view-specific tabs
|
||||||
|
appui.setContentTabs([...]);
|
||||||
|
|
||||||
|
// Load data based on route params
|
||||||
|
if (params?.section) {
|
||||||
|
await this.loadSection(params.section);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when view is deactivated (hidden)
|
||||||
|
*/
|
||||||
|
onDeactivate() {
|
||||||
|
this.cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called before navigation away
|
||||||
|
* Return false or a message string to block navigation
|
||||||
|
*/
|
||||||
|
canDeactivate(): boolean | string {
|
||||||
|
if (this.hasUnsavedChanges) {
|
||||||
|
return 'You have unsaved changes. Leave anyway?';
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### IViewActivationContext
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface IViewActivationContext {
|
||||||
|
appui: DeesAppuiBase; // Reference to the app shell
|
||||||
|
viewId: string; // The view ID being activated
|
||||||
|
params?: Record<string, string>; // Route parameters
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Routing
|
||||||
|
|
||||||
|
Routes are automatically registered from view definitions using `domtools.router`.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const views = [
|
||||||
|
{ id: 'dashboard', route: 'dashboard', ... },
|
||||||
|
{ id: 'settings', route: 'settings/:section?', ... }, // Parameterized
|
||||||
|
{ id: 'user', route: 'users/:id', ... },
|
||||||
|
];
|
||||||
|
|
||||||
|
// URL: #dashboard → navigates to dashboard view
|
||||||
|
// URL: #settings/profile → navigates to settings with params.section = 'profile'
|
||||||
|
// URL: #users/123 → navigates to user with params.id = '123'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Hash-based Routing
|
||||||
|
|
||||||
|
The router uses hash-based routing by default (`#viewId`). URLs are automatically synchronized when navigating via `navigateToView()`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## View Caching
|
||||||
|
|
||||||
|
Views are cached by default. When navigating away and back, the same DOM element is reused (hidden/shown) rather than destroyed and recreated.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Disable caching for a specific view
|
||||||
|
{
|
||||||
|
id: 'reports',
|
||||||
|
name: 'Reports',
|
||||||
|
content: 'my-reports-view',
|
||||||
|
cache: false // Always recreate this view
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Lazy Loading
|
||||||
|
|
||||||
|
Use async content functions for lazy loading views.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{
|
||||||
|
id: 'analytics',
|
||||||
|
name: 'Analytics',
|
||||||
|
content: async () => {
|
||||||
|
const module = await import('./views/analytics.js');
|
||||||
|
return module.AnalyticsView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## RxJS Observables
|
||||||
|
|
||||||
|
The component exposes RxJS Subjects for reactive programming.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// View lifecycle events
|
||||||
|
appui.viewLifecycle$.subscribe((event) => {
|
||||||
|
// event.type: 'loading' | 'activated' | 'deactivated' | 'loaded' | 'loadError'
|
||||||
|
// event.viewId: string
|
||||||
|
// event.element?: HTMLElement
|
||||||
|
// event.params?: Record<string, string>
|
||||||
|
// event.error?: unknown
|
||||||
|
});
|
||||||
|
|
||||||
|
// View change events
|
||||||
|
appui.viewChanged$.subscribe((event) => {
|
||||||
|
// event.viewId: string
|
||||||
|
// event.view: IViewDefinition
|
||||||
|
// event.previousView?: IViewDefinition
|
||||||
|
// event.params?: Record<string, string>
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Complete Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { html, DeesElement, customElement } from '@design.estate/dees-element';
|
||||||
|
import { DeesAppuiBase, IViewActivationContext } from '@design.estate/dees-catalog';
|
||||||
|
|
||||||
|
@customElement('my-app')
|
||||||
|
class MyApp extends DeesElement {
|
||||||
|
private appui: DeesAppuiBase;
|
||||||
|
|
||||||
|
async firstUpdated() {
|
||||||
|
this.appui = this.shadowRoot.querySelector('dees-appui-base');
|
||||||
|
|
||||||
|
this.appui.configure({
|
||||||
|
branding: {
|
||||||
|
logoIcon: 'lucide:briefcase',
|
||||||
|
logoText: 'CRM Pro'
|
||||||
|
},
|
||||||
|
|
||||||
|
appBar: {
|
||||||
|
menuItems: [
|
||||||
|
{ name: 'File', submenu: [...] },
|
||||||
|
{ name: 'Edit', submenu: [...] }
|
||||||
|
],
|
||||||
|
showSearch: true,
|
||||||
|
user: { name: 'Jane Smith', status: 'online' }
|
||||||
|
},
|
||||||
|
|
||||||
|
views: [
|
||||||
|
{
|
||||||
|
id: 'dashboard',
|
||||||
|
name: 'Dashboard',
|
||||||
|
iconName: 'lucide:home',
|
||||||
|
content: 'crm-dashboard',
|
||||||
|
route: 'dashboard'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'contacts',
|
||||||
|
name: 'Contacts',
|
||||||
|
iconName: 'lucide:users',
|
||||||
|
content: 'crm-contacts',
|
||||||
|
route: 'contacts',
|
||||||
|
badge: 42
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'settings',
|
||||||
|
name: 'Settings',
|
||||||
|
iconName: 'lucide:settings',
|
||||||
|
content: 'crm-settings',
|
||||||
|
route: 'settings/:section?'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
mainMenu: {
|
||||||
|
sections: [
|
||||||
|
{ name: 'Main', views: ['dashboard', 'contacts'] }
|
||||||
|
],
|
||||||
|
bottomItems: ['settings']
|
||||||
|
},
|
||||||
|
|
||||||
|
defaultView: 'dashboard',
|
||||||
|
|
||||||
|
onViewChange: (viewId, view) => {
|
||||||
|
console.log(`Navigated to: ${view.name}`);
|
||||||
|
},
|
||||||
|
|
||||||
|
onSearch: (query) => {
|
||||||
|
console.log(`Search: ${query}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load activity from backend
|
||||||
|
const activities = await fetch('/api/activities').then(r => r.json());
|
||||||
|
this.appui.activityLog.addMany(activities);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`<dees-appui-base></dees-appui-base>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// View with lifecycle hooks
|
||||||
|
@customElement('crm-settings')
|
||||||
|
class CrmSettings extends DeesElement {
|
||||||
|
private appui: DeesAppuiBase;
|
||||||
|
|
||||||
|
onActivate(context: IViewActivationContext) {
|
||||||
|
this.appui = context.appui;
|
||||||
|
|
||||||
|
// Set secondary menu for settings
|
||||||
|
this.appui.setSecondaryMenu({
|
||||||
|
heading: 'Settings',
|
||||||
|
groups: [
|
||||||
|
{
|
||||||
|
name: 'Account',
|
||||||
|
items: [
|
||||||
|
{ key: 'profile', iconName: 'lucide:user', action: () => this.showSection('profile') },
|
||||||
|
{ key: 'security', iconName: 'lucide:shield', action: () => this.showSection('security') }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Preferences',
|
||||||
|
items: [
|
||||||
|
{ key: 'notifications', iconName: 'lucide:bell', action: () => this.showSection('notifications') }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Navigate to section from URL params
|
||||||
|
if (context.params?.section) {
|
||||||
|
this.showSection(context.params.section);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showSection(section: string) {
|
||||||
|
this.appui.setSecondaryMenuSelection(section);
|
||||||
|
// ... load section content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TypeScript Types
|
||||||
|
|
||||||
|
All interfaces are exported from `@design.estate/dees-catalog`:
|
||||||
|
|
||||||
|
- `IAppConfig` - Main configuration
|
||||||
|
- `IViewDefinition` - View definition
|
||||||
|
- `IViewActivationContext` - Context passed to `onActivate`
|
||||||
|
- `IViewLifecycle` - Lifecycle hooks interface
|
||||||
|
- `IViewLifecycleEvent` - Lifecycle event for rxjs Subject
|
||||||
|
- `IViewChangeEvent` - View change event
|
||||||
|
- `IAppUser` - User configuration
|
||||||
|
- `IActivityEntry` - Activity log entry
|
||||||
|
- `IActivityLogAPI` - Activity log methods
|
||||||
|
- `IAppBarMenuItem` - App bar menu item
|
||||||
|
- `IMainMenuConfig` - Main menu configuration
|
||||||
|
- `ISecondaryMenuGroup` - Secondary menu group
|
||||||
|
- `ITab` - Tab definition
|
||||||
402
ts_web/elements/00group-appui/dees-appui-base/view.registry.ts
Normal file
402
ts_web/elements/00group-appui/dees-appui-base/view.registry.ts
Normal file
@@ -0,0 +1,402 @@
|
|||||||
|
import { html, render, type TemplateResult } from '@design.estate/dees-element';
|
||||||
|
import type {
|
||||||
|
IViewDefinition,
|
||||||
|
IViewActivationContext,
|
||||||
|
IViewLifecycle,
|
||||||
|
TDeesAppuiBase
|
||||||
|
} from '../../interfaces/appconfig.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registry for managing views and their lifecycle
|
||||||
|
*
|
||||||
|
* Key features:
|
||||||
|
* - View caching with hide/show pattern (not destroy/create)
|
||||||
|
* - Async content loading support (lazy loading)
|
||||||
|
* - View lifecycle hooks (onActivate, onDeactivate, canDeactivate)
|
||||||
|
*/
|
||||||
|
export class ViewRegistry {
|
||||||
|
private views: Map<string, IViewDefinition> = new Map();
|
||||||
|
private instances: Map<string, HTMLElement> = new Map();
|
||||||
|
private currentViewId: string | null = null;
|
||||||
|
private appui: TDeesAppuiBase | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the appui reference for view activation context
|
||||||
|
*/
|
||||||
|
public setAppuiRef(appui: TDeesAppuiBase): void {
|
||||||
|
this.appui = appui;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a single view
|
||||||
|
*/
|
||||||
|
public register(view: IViewDefinition): void {
|
||||||
|
if (this.views.has(view.id)) {
|
||||||
|
console.warn(`View with id "${view.id}" already registered. Overwriting.`);
|
||||||
|
}
|
||||||
|
this.views.set(view.id, view);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register multiple views
|
||||||
|
*/
|
||||||
|
public registerAll(views: IViewDefinition[]): void {
|
||||||
|
views.forEach((view) => this.register(view));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a view definition by ID
|
||||||
|
*/
|
||||||
|
public get(viewId: string): IViewDefinition | undefined {
|
||||||
|
return this.views.get(viewId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all registered view IDs
|
||||||
|
*/
|
||||||
|
public getViewIds(): string[] {
|
||||||
|
return Array.from(this.views.keys());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all views
|
||||||
|
*/
|
||||||
|
public getAll(): IViewDefinition[] {
|
||||||
|
return Array.from(this.views.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get route for a view
|
||||||
|
*/
|
||||||
|
public getRoute(viewId: string): string {
|
||||||
|
const view = this.views.get(viewId);
|
||||||
|
return view?.route || view?.id || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find view by route (supports parameterized routes like 'settings/:section')
|
||||||
|
*/
|
||||||
|
public findByRoute(route: string): { view: IViewDefinition; params: Record<string, string> } | undefined {
|
||||||
|
for (const view of this.views.values()) {
|
||||||
|
const viewRoute = view.route || view.id;
|
||||||
|
const params = this.matchRoute(viewRoute, route);
|
||||||
|
if (params !== null) {
|
||||||
|
return { view, params };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Match a route pattern against an actual route
|
||||||
|
* Returns params if matched, null otherwise
|
||||||
|
*/
|
||||||
|
private matchRoute(pattern: string, route: string): Record<string, string> | null {
|
||||||
|
const patternParts = pattern.split('/');
|
||||||
|
const routeParts = route.split('/');
|
||||||
|
|
||||||
|
// Check for optional trailing param (ends with ?)
|
||||||
|
const hasOptionalParam = patternParts.length > 0 &&
|
||||||
|
patternParts[patternParts.length - 1].endsWith('?');
|
||||||
|
|
||||||
|
if (hasOptionalParam) {
|
||||||
|
// Allow route to be shorter by 1
|
||||||
|
if (routeParts.length < patternParts.length - 1 || routeParts.length > patternParts.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else if (patternParts.length !== routeParts.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const params: Record<string, string> = {};
|
||||||
|
|
||||||
|
for (let i = 0; i < patternParts.length; i++) {
|
||||||
|
let part = patternParts[i];
|
||||||
|
const isOptional = part.endsWith('?');
|
||||||
|
if (isOptional) {
|
||||||
|
part = part.slice(0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (part.startsWith(':')) {
|
||||||
|
// This is a parameter
|
||||||
|
const paramName = part.slice(1);
|
||||||
|
if (routeParts[i] !== undefined) {
|
||||||
|
params[paramName] = routeParts[i];
|
||||||
|
} else if (!isOptional) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else if (routeParts[i] !== part) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if navigation away from current view is allowed
|
||||||
|
*/
|
||||||
|
public async canLeaveCurrentView(): Promise<boolean | string> {
|
||||||
|
if (!this.currentViewId) return true;
|
||||||
|
|
||||||
|
const instance = this.instances.get(this.currentViewId);
|
||||||
|
if (!instance) return true;
|
||||||
|
|
||||||
|
const lifecycle = instance as unknown as IViewLifecycle;
|
||||||
|
if (typeof lifecycle.canDeactivate === 'function') {
|
||||||
|
return await lifecycle.canDeactivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate a view - handles caching, lifecycle, and rendering
|
||||||
|
*/
|
||||||
|
public async activateView(
|
||||||
|
viewId: string,
|
||||||
|
container: HTMLElement,
|
||||||
|
params?: Record<string, string>
|
||||||
|
): Promise<HTMLElement | null> {
|
||||||
|
const view = this.views.get(viewId);
|
||||||
|
if (!view) {
|
||||||
|
console.error(`View "${viewId}" not found in registry`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if caching is enabled for this view (default: true)
|
||||||
|
const shouldCache = view.cache !== false;
|
||||||
|
|
||||||
|
// Deactivate current view
|
||||||
|
if (this.currentViewId && this.currentViewId !== viewId) {
|
||||||
|
await this.deactivateView(this.currentViewId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for cached instance
|
||||||
|
let element = shouldCache ? this.instances.get(viewId) : undefined;
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
// Reuse cached instance - just show it
|
||||||
|
element.style.display = '';
|
||||||
|
} else {
|
||||||
|
// Create new instance
|
||||||
|
element = await this.createViewElement(view);
|
||||||
|
if (!element) {
|
||||||
|
console.error(`Failed to create element for view "${viewId}"`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to container
|
||||||
|
container.appendChild(element);
|
||||||
|
|
||||||
|
// Cache if enabled
|
||||||
|
if (shouldCache) {
|
||||||
|
this.instances.set(viewId, element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentViewId = viewId;
|
||||||
|
|
||||||
|
// Call onActivate lifecycle hook
|
||||||
|
await this.callOnActivate(element, viewId, params);
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deactivate a view (hide and call lifecycle hook)
|
||||||
|
*/
|
||||||
|
private async deactivateView(viewId: string): Promise<void> {
|
||||||
|
const instance = this.instances.get(viewId);
|
||||||
|
if (!instance) return;
|
||||||
|
|
||||||
|
// Call onDeactivate lifecycle hook
|
||||||
|
const lifecycle = instance as unknown as IViewLifecycle;
|
||||||
|
if (typeof lifecycle.onDeactivate === 'function') {
|
||||||
|
await lifecycle.onDeactivate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide the element
|
||||||
|
instance.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a view element from its definition (supports async content)
|
||||||
|
*/
|
||||||
|
private async createViewElement(view: IViewDefinition): Promise<HTMLElement | null> {
|
||||||
|
let content = view.content;
|
||||||
|
|
||||||
|
// Handle async content (lazy loading)
|
||||||
|
if (typeof content === 'function' &&
|
||||||
|
!(content.prototype instanceof HTMLElement) &&
|
||||||
|
content.constructor.name === 'AsyncFunction') {
|
||||||
|
try {
|
||||||
|
content = await (content as () => Promise<string | (new () => HTMLElement) | (() => TemplateResult)>)();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to load async content for view "${view.id}":`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let element: HTMLElement;
|
||||||
|
|
||||||
|
if (typeof content === 'string') {
|
||||||
|
// Tag name string
|
||||||
|
element = document.createElement(content);
|
||||||
|
} else if (typeof content === 'function') {
|
||||||
|
// Check if it's a class constructor or template function
|
||||||
|
if (content.prototype instanceof HTMLElement) {
|
||||||
|
// Element class constructor
|
||||||
|
element = new (content as new () => HTMLElement)();
|
||||||
|
} else {
|
||||||
|
// Template function - wrap in a container and use Lit's render
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'view-content-wrapper';
|
||||||
|
wrapper.style.cssText = 'display: contents;';
|
||||||
|
const template = (content as () => TemplateResult)();
|
||||||
|
render(template, wrapper);
|
||||||
|
element = wrapper;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error(`Invalid content type for view "${view.id}"`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add view ID as data attribute for debugging
|
||||||
|
element.dataset.viewId = view.id;
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call onActivate lifecycle hook on a view element
|
||||||
|
*/
|
||||||
|
private async callOnActivate(
|
||||||
|
element: HTMLElement,
|
||||||
|
viewId: string,
|
||||||
|
params?: Record<string, string>
|
||||||
|
): Promise<void> {
|
||||||
|
const lifecycle = element as unknown as IViewLifecycle;
|
||||||
|
if (typeof lifecycle.onActivate === 'function') {
|
||||||
|
const context: IViewActivationContext = {
|
||||||
|
appui: this.appui!,
|
||||||
|
viewId,
|
||||||
|
params,
|
||||||
|
};
|
||||||
|
await lifecycle.onActivate(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Legacy method - renders view without caching
|
||||||
|
* @deprecated Use activateView instead
|
||||||
|
*/
|
||||||
|
public renderView(viewId: string, container: HTMLElement): HTMLElement | null {
|
||||||
|
const view = this.views.get(viewId);
|
||||||
|
if (!view) {
|
||||||
|
console.error(`View "${viewId}" not found in registry`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For legacy compatibility, clear container
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
let element: HTMLElement;
|
||||||
|
const content = view.content;
|
||||||
|
|
||||||
|
if (typeof content === 'string') {
|
||||||
|
element = document.createElement(content);
|
||||||
|
} else if (typeof content === 'function') {
|
||||||
|
if ((content as any).prototype instanceof HTMLElement) {
|
||||||
|
element = new (content as new () => HTMLElement)();
|
||||||
|
} else {
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'view-content-wrapper';
|
||||||
|
wrapper.style.cssText = 'display: contents;';
|
||||||
|
const template = (content as () => TemplateResult)();
|
||||||
|
render(template, wrapper);
|
||||||
|
element = wrapper;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error(`Invalid content type for view "${viewId}"`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.appendChild(element);
|
||||||
|
this.instances.set(viewId, element);
|
||||||
|
this.currentViewId = viewId;
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get currently active view ID
|
||||||
|
*/
|
||||||
|
public getCurrentViewId(): string | null {
|
||||||
|
return this.currentViewId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cached instance of a view
|
||||||
|
*/
|
||||||
|
public getInstance(viewId: string): HTMLElement | undefined {
|
||||||
|
return this.instances.get(viewId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear a specific cached instance
|
||||||
|
*/
|
||||||
|
public clearInstance(viewId: string): void {
|
||||||
|
const instance = this.instances.get(viewId);
|
||||||
|
if (instance && instance.parentNode) {
|
||||||
|
instance.parentNode.removeChild(instance);
|
||||||
|
}
|
||||||
|
this.instances.delete(viewId);
|
||||||
|
if (this.currentViewId === viewId) {
|
||||||
|
this.currentViewId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all instances
|
||||||
|
*/
|
||||||
|
public clearInstances(): void {
|
||||||
|
for (const [viewId, instance] of this.instances) {
|
||||||
|
if (instance.parentNode) {
|
||||||
|
instance.parentNode.removeChild(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.instances.clear();
|
||||||
|
this.currentViewId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister a view
|
||||||
|
*/
|
||||||
|
public unregister(viewId: string): boolean {
|
||||||
|
this.clearInstance(viewId);
|
||||||
|
return this.views.delete(viewId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the registry
|
||||||
|
*/
|
||||||
|
public clear(): void {
|
||||||
|
this.views.clear();
|
||||||
|
this.clearInstances();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a view is registered
|
||||||
|
*/
|
||||||
|
public has(viewId: string): boolean {
|
||||||
|
return this.views.has(viewId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the number of registered views
|
||||||
|
*/
|
||||||
|
public get size(): number {
|
||||||
|
return this.views.size;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,6 +57,7 @@ export class DeesAppuiMainmenu extends DeesElement {
|
|||||||
--menu-width-collapsed: 56px;
|
--menu-width-collapsed: 56px;
|
||||||
--tooltip-bg: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
--tooltip-bg: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
||||||
--tooltip-fg: ${cssManager.bdTheme('#fafafa', '#18181b')};
|
--tooltip-fg: ${cssManager.bdTheme('#fafafa', '#18181b')};
|
||||||
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ export class DeesAppuiSecondarymenu extends DeesElement {
|
|||||||
--badge-error-bg: ${cssManager.bdTheme('#fee2e2', '#450a0a')};
|
--badge-error-bg: ${cssManager.bdTheme('#fee2e2', '#450a0a')};
|
||||||
--badge-error-fg: ${cssManager.bdTheme('#991b1b', '#f87171')};
|
--badge-error-fg: ${cssManager.bdTheme('#991b1b', '#f87171')};
|
||||||
|
|
||||||
|
position: relative;
|
||||||
display: block;
|
display: block;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: var(--sidebar-width-expanded);
|
width: var(--sidebar-width-expanded);
|
||||||
|
|||||||
@@ -299,6 +299,8 @@ export class DeesPdfViewer extends DeesElement {
|
|||||||
await this.renderThumbnails();
|
await this.renderThumbnails();
|
||||||
// Re-setup intersection observer for lazy loading of pages
|
// Re-setup intersection observer for lazy loading of pages
|
||||||
this.setupIntersectionObserver();
|
this.setupIntersectionObserver();
|
||||||
|
// Scroll to active thumbnail after rendering
|
||||||
|
this.scrollThumbnailIntoView(this.currentPage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -578,6 +580,11 @@ export class DeesPdfViewer extends DeesElement {
|
|||||||
// Update current page
|
// Update current page
|
||||||
this.currentPage = pageNum;
|
this.currentPage = pageNum;
|
||||||
|
|
||||||
|
// Scroll thumbnail into view if sidebar is visible
|
||||||
|
if (this.showSidebar) {
|
||||||
|
this.scrollThumbnailIntoView(pageNum);
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure the page is rendered
|
// Ensure the page is rendered
|
||||||
await this.renderPageIfNeeded(pageNum);
|
await this.renderPageIfNeeded(pageNum);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -266,17 +266,17 @@ export const demoFunc = () => html`
|
|||||||
.viewTabs=${[
|
.viewTabs=${[
|
||||||
{
|
{
|
||||||
name: 'Dashboard',
|
name: 'Dashboard',
|
||||||
iconName: 'home',
|
iconName: 'lucide:home',
|
||||||
element: DemoViewDashboard,
|
element: DemoViewDashboard,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Analytics',
|
name: 'Analytics',
|
||||||
iconName: 'lineChart',
|
iconName: 'lucide:lineChart',
|
||||||
element: DemoViewAnalytics,
|
element: DemoViewAnalytics,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
iconName: 'settings',
|
iconName: 'lucide:settings',
|
||||||
element: DemoViewSettings,
|
element: DemoViewSettings,
|
||||||
}
|
}
|
||||||
] as IView[]}
|
] as IView[]}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export class DeesSimpleAppDash extends DeesElement {
|
|||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
|
||||||
user-select: none;
|
user-select: none;
|
||||||
display: block;
|
display: block;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -75,109 +75,188 @@ export class DeesSimpleAppDash extends DeesElement {
|
|||||||
left: 0px;
|
left: 0px;
|
||||||
height: calc(100% - 24px);
|
height: calc(100% - 24px);
|
||||||
width: 240px;
|
width: 240px;
|
||||||
background: ${cssManager.bdTheme('#fafafa', '#000')};
|
background: ${cssManager.bdTheme('hsl(0 0% 99%)', 'hsl(0 0% 7%)')};
|
||||||
border-right: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')};
|
border-right: 1px solid ${cssManager.bdTheme('hsl(0 0% 91%)', 'hsl(0 0% 13%)')};
|
||||||
font-size: 12px;
|
font-size: 13px;
|
||||||
font-family: 'Geist Sans', sans-serif;
|
font-family: 'Geist Sans', sans-serif;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: auto min-content;
|
grid-template-rows: auto 1fr min-content;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-header {
|
.sidebar-header {
|
||||||
padding: 16px 12px;
|
padding: 20px 16px;
|
||||||
border-bottom: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')};
|
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 91%)', 'hsl(0 0% 13%)')};
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-icon-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: ${cssManager.bdTheme(
|
||||||
|
'linear-gradient(135deg, hsl(215 20% 95%) 0%, hsl(215 20% 90%) 100%)',
|
||||||
|
'linear-gradient(135deg, hsl(215 20% 18%) 0%, hsl(215 20% 14%) 100%)'
|
||||||
|
)};
|
||||||
|
box-shadow: ${cssManager.bdTheme(
|
||||||
|
'0 1px 2px rgb(0 0 0 / 0.05), inset 0 1px 0 rgb(255 255 255 / 0.5)',
|
||||||
|
'0 1px 2px rgb(0 0 0 / 0.2), inset 0 1px 0 rgb(255 255 255 / 0.05)'
|
||||||
|
)};
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-icon-wrapper dees-icon {
|
||||||
|
font-size: 18px;
|
||||||
|
color: ${cssManager.bdTheme('hsl(215 20% 40%)', 'hsl(215 20% 70%)')};
|
||||||
|
}
|
||||||
|
|
||||||
.appName {
|
.appName {
|
||||||
font-size: 14px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: ${cssManager.bdTheme('#000', '#fff')};
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 98%)')};
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.viewTabs-container {
|
.viewTabs-container {
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 4px 0;
|
padding: 12px 8px;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 20%)')} transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewTabs-container::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewTabs-container::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewTabs-container::-webkit-scrollbar-thumb {
|
||||||
|
background: ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 20%)')};
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewTabs-container::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: ${cssManager.bdTheme('hsl(0 0% 75%)', 'hsl(0 0% 30%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-label {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 50%)', 'hsl(0 0% 50%)')};
|
||||||
|
padding: 8px 12px 8px;
|
||||||
|
margin-bottom: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.viewTabs {
|
.viewTabs {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.viewTab {
|
.viewTab {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 10px;
|
||||||
padding: 8px 12px;
|
padding: 10px 12px;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
transition: background 0.1s;
|
transition: all 0.15s ease;
|
||||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 65%)')};
|
||||||
user-select: none;
|
user-select: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.viewTab:hover {
|
.viewTab:hover {
|
||||||
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.04)', 'rgba(255, 255, 255, 0.08)')};
|
background: ${cssManager.bdTheme('hsl(0 0% 0% / 0.04)', 'hsl(0 0% 100% / 0.05)')};
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.viewTab:active {
|
.viewTab:active {
|
||||||
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.08)', 'rgba(255, 255, 255, 0.12)')};
|
background: ${cssManager.bdTheme('hsl(0 0% 0% / 0.06)', 'hsl(0 0% 100% / 0.07)')};
|
||||||
|
transform: scale(0.99);
|
||||||
}
|
}
|
||||||
|
|
||||||
.viewTab.selected {
|
.viewTab.selected {
|
||||||
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.08)', 'rgba(255, 255, 255, 0.12)')};
|
background: ${cssManager.bdTheme('hsl(215 25% 95%)', 'hsl(215 20% 15%)')};
|
||||||
color: ${cssManager.bdTheme('#000', '#fff')};
|
color: ${cssManager.bdTheme('hsl(215 25% 30%)', 'hsl(215 25% 85%)')};
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.viewTab.selected::before {
|
.viewTab.selected::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 8px;
|
||||||
bottom: 0;
|
bottom: 8px;
|
||||||
width: 3px;
|
width: 3px;
|
||||||
background: ${cssManager.bdTheme('#26a69a', '#26a69a')};
|
border-radius: 0 2px 2px 0;
|
||||||
}
|
background: ${cssManager.bdTheme('hsl(215 70% 50%)', 'hsl(215 70% 60%)')};
|
||||||
|
|
||||||
.viewTab dees-icon {
|
|
||||||
font-size: 14px;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.viewTab dees-icon {
|
||||||
|
font-size: 16px;
|
||||||
|
opacity: 0.55;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewTab:hover dees-icon {
|
||||||
|
opacity: 0.75;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewTab.selected dees-icon {
|
||||||
|
opacity: 0.9;
|
||||||
|
color: ${cssManager.bdTheme('hsl(215 70% 45%)', 'hsl(215 70% 65%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewTab span {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.appActions {
|
.appActions {
|
||||||
padding: 12px;
|
padding: 12px 8px;
|
||||||
border-top: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')};
|
border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 91%)', 'hsl(0 0% 13%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.action {
|
.action {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 10px;
|
||||||
padding: 8px;
|
padding: 10px 12px;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
transition: background 0.1s;
|
transition: all 0.15s ease;
|
||||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 65%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.action:hover {
|
.action:hover {
|
||||||
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.04)', 'rgba(255, 255, 255, 0.08)')};
|
background: ${cssManager.bdTheme('hsl(0 84% 60% / 0.08)', 'hsl(0 84% 60% / 0.12)')};
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 84% 45%)', 'hsl(0 84% 65%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.action dees-icon {
|
.action dees-icon {
|
||||||
font-size: 14px;
|
font-size: 16px;
|
||||||
opacity: 0.7;
|
opacity: 0.6;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action:hover dees-icon {
|
||||||
|
opacity: 0.9;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 84% 45%)', 'hsl(0 84% 65%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.appcontent {
|
.appcontent {
|
||||||
@@ -189,46 +268,61 @@ export class DeesSimpleAppDash extends DeesElement {
|
|||||||
bottom: 24px;
|
bottom: 24px;
|
||||||
width: calc(100% - 240px);
|
width: calc(100% - 240px);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
background: ${cssManager.bdTheme('#f5f5f5', '#000')};
|
background: ${cssManager.bdTheme('hsl(0 0% 97%)', 'hsl(0 0% 5%)')};
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.controlbar {
|
.controlbar {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-top: 1px solid ${cssManager.bdTheme('#00000020', '#ffffff20')};
|
|
||||||
height: 24px;
|
height: 24px;
|
||||||
background: ${cssManager.bdTheme('#2196f3', '#1565c0')};
|
background: ${cssManager.bdTheme('hsl(220 13% 18%)', 'hsl(220 13% 12%)')};
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.01em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.control {
|
.control {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 6px;
|
||||||
margin-right: 16px;
|
padding: 0 12px;
|
||||||
|
height: 100%;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
opacity: 0.8;
|
color: hsl(0 0% 70%);
|
||||||
transition: opacity 0.2s;
|
transition: all 0.15s ease;
|
||||||
}
|
border-left: 1px solid hsl(0 0% 100% / 0.08);
|
||||||
|
|
||||||
.control:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control dees-icon {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.control:first-child {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control:hover {
|
||||||
|
background: hsl(0 0% 100% / 0.06);
|
||||||
|
color: hsl(0 0% 95%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.control dees-icon {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control.status-connected dees-icon {
|
||||||
|
color: hsl(142 70% 50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.control.status-terminal dees-icon {
|
||||||
|
color: hsl(45 90% 55%);
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -236,27 +330,30 @@ export class DeesSimpleAppDash extends DeesElement {
|
|||||||
return html`
|
return html`
|
||||||
<div class="maincontainer">
|
<div class="maincontainer">
|
||||||
<div class="appbar">
|
<div class="appbar">
|
||||||
<div>
|
<div class="sidebar-header">
|
||||||
<div class="sidebar-header">
|
<div class="header-icon-wrapper">
|
||||||
<dees-icon .icon="${'lucide:grid3x3'}" style="font-size: 18px;"></dees-icon>
|
<dees-icon .icon="${'lucide:layoutGrid'}"></dees-icon>
|
||||||
<div class="appName">${this.name}</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="viewTabs-container">
|
<div class="appName">${this.name}</div>
|
||||||
<div class="viewTabs">
|
</div>
|
||||||
${this.viewTabs.map(
|
<div class="viewTabs-container">
|
||||||
(view) => html`
|
<div class="section-label">Navigation</div>
|
||||||
<div
|
<div class="viewTabs">
|
||||||
class="viewTab ${this.selectedView === view ? 'selected' : ''}"
|
${this.viewTabs.map(
|
||||||
@click=${() => this.loadView(view)}
|
(view) => html`
|
||||||
>
|
<div
|
||||||
${view.iconName ? html`
|
class="viewTab ${this.selectedView === view ? 'selected' : ''}"
|
||||||
<dees-icon .icon="${`${view.iconName}`}"></dees-icon>
|
@click=${() => this.loadView(view)}
|
||||||
` : ''}
|
>
|
||||||
<span style="flex: 1;">${view.name}</span>
|
${view.iconName ? html`
|
||||||
</div>
|
<dees-icon .icon="${view.iconName.includes(':') ? view.iconName : `lucide:${view.iconName}`}"></dees-icon>
|
||||||
`
|
` : html`
|
||||||
)}
|
<dees-icon .icon="${'lucide:file'}"></dees-icon>
|
||||||
</div>
|
`}
|
||||||
|
<span>${view.name}</span>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="appActions">
|
<div class="appActions">
|
||||||
@@ -264,7 +361,7 @@ export class DeesSimpleAppDash extends DeesElement {
|
|||||||
this.dispatchEvent(new CustomEvent('logout', { bubbles: true, composed: true }));
|
this.dispatchEvent(new CustomEvent('logout', { bubbles: true, composed: true }));
|
||||||
}}>
|
}}>
|
||||||
<dees-icon .icon="${'lucide:logOut'}"></dees-icon>
|
<dees-icon .icon="${'lucide:logOut'}"></dees-icon>
|
||||||
<span>Logout</span>
|
<span>Sign out</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -272,11 +369,11 @@ export class DeesSimpleAppDash extends DeesElement {
|
|||||||
<!-- Content goes here -->
|
<!-- Content goes here -->
|
||||||
</div>
|
</div>
|
||||||
<div class="controlbar">
|
<div class="controlbar">
|
||||||
<div class="control">
|
<div class="control status-connected">
|
||||||
<dees-icon .icon="${'lucide:wifi'}"></dees-icon>
|
<dees-icon .icon="${'lucide:wifi'}"></dees-icon>
|
||||||
<span>Connected</span>
|
<span>Connected</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="control" @click=${this.launchTerminal}>
|
<div class="control status-terminal" @click=${this.launchTerminal}>
|
||||||
<dees-icon .icon="${'lucide:terminal'}"></dees-icon>
|
<dees-icon .icon="${'lucide:terminal'}"></dees-icon>
|
||||||
<span>Terminal</span>
|
<span>Terminal</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -315,10 +412,10 @@ export class DeesSimpleAppDash extends DeesElement {
|
|||||||
terminal.style.right = '0px';
|
terminal.style.right = '0px';
|
||||||
terminal.style.bottom = '24px';
|
terminal.style.bottom = '24px';
|
||||||
terminal.style.opacity = '0';
|
terminal.style.opacity = '0';
|
||||||
terminal.style.transform = 'translateY(20px)';
|
terminal.style.transform = 'translateY(8px) scale(0.99)';
|
||||||
terminal.style.transition = 'all 0.2s';
|
terminal.style.transition = 'all 0.25s cubic-bezier(0.4, 0, 0.2, 1)';
|
||||||
terminal.style.background = '#000';
|
terminal.background = 'hsl(220 13% 8%)';
|
||||||
terminal.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.3)';
|
terminal.style.boxShadow = '0 25px 50px -12px rgb(0 0 0 / 0.5), 0 0 0 1px rgb(255 255 255 / 0.05)';
|
||||||
terminal.style.maxWidth = `calc(${maincontainer.clientWidth}px -240px)`;
|
terminal.style.maxWidth = `calc(${maincontainer.clientWidth}px -240px)`;
|
||||||
terminal.style.maxHeight = `calc(${maincontainer.clientHeight}px - 24px)`;
|
terminal.style.maxHeight = `calc(${maincontainer.clientHeight}px - 24px)`;
|
||||||
|
|
||||||
@@ -327,7 +424,7 @@ export class DeesSimpleAppDash extends DeesElement {
|
|||||||
|
|
||||||
await domtools.convenience.smartdelay.delayFor(0);
|
await domtools.convenience.smartdelay.delayFor(0);
|
||||||
terminal.style.opacity = '1';
|
terminal.style.opacity = '1';
|
||||||
terminal.style.transform = 'translateY(0px)';
|
terminal.style.transform = 'translateY(0) scale(1)';
|
||||||
return terminal;
|
return terminal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,8 +432,8 @@ export class DeesSimpleAppDash extends DeesElement {
|
|||||||
const domtools = await this.domtoolsPromise;
|
const domtools = await this.domtoolsPromise;
|
||||||
if (this.currentTerminal) {
|
if (this.currentTerminal) {
|
||||||
this.currentTerminal.style.opacity = '0';
|
this.currentTerminal.style.opacity = '0';
|
||||||
this.currentTerminal.style.transform = 'translateY(20px)';
|
this.currentTerminal.style.transform = 'translateY(8px) scale(0.99)';
|
||||||
await domtools.convenience.smartdelay.delayFor(200);
|
await domtools.convenience.smartdelay.delayFor(250);
|
||||||
this.currentTerminal.remove();
|
this.currentTerminal.remove();
|
||||||
this.currentTerminal = null;
|
this.currentTerminal = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,14 +29,14 @@ export class DeesSimpleLogin extends DeesElement {
|
|||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
color: ${cssManager.bdTheme('#333', '#ccc')};
|
color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
|
||||||
user-select: none;
|
user-select: none;
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-family: 'Geist Sans', sans-serif;
|
font-family: 'Geist Sans', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loginContainer {
|
.loginContainer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -46,7 +46,7 @@ export class DeesSimpleLogin extends DeesElement {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
background: ${cssManager.bdTheme('#f5f5f5', '#000')};
|
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.slotContainer {
|
.slotContainer {
|
||||||
@@ -56,43 +56,56 @@ export class DeesSimpleLogin extends DeesElement {
|
|||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 0.3s, transform 0.3s;
|
transition: opacity 0.2s ease, transform 0.2s ease;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login {
|
.login {
|
||||||
min-width: 320px;
|
width: 100%;
|
||||||
background: ${cssManager.bdTheme('#ffffff', '#111')};
|
max-width: 360px;
|
||||||
box-shadow: ${cssManager.bdTheme(
|
display: flex;
|
||||||
'0 4px 12px rgba(0, 0, 0, 0.15)',
|
flex-direction: column;
|
||||||
'0 4px 12px rgba(0, 0, 0, 0.3)'
|
gap: 24px;
|
||||||
)};
|
}
|
||||||
border-radius: 8px;
|
|
||||||
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')};
|
.login-header {
|
||||||
padding: 24px;
|
display: flex;
|
||||||
transition: opacity 0.3s, transform 0.3s;
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
text-align: center;
|
font-size: 24px;
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 20px;
|
letter-spacing: -0.025em;
|
||||||
color: ${cssManager.bdTheme('#000', '#fff')};
|
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 98%)')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.login dees-form {
|
.subheader {
|
||||||
|
font-size: 14px;
|
||||||
|
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card {
|
||||||
|
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')};
|
||||||
|
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-card dees-form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login dees-input-text {
|
.login-card dees-input-text {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login dees-form-submit {
|
.login-card dees-form-submit {
|
||||||
margin-top: 4px;
|
margin-top: 8px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@@ -102,12 +115,17 @@ export class DeesSimpleLogin extends DeesElement {
|
|||||||
return html`
|
return html`
|
||||||
<div class="loginContainer">
|
<div class="loginContainer">
|
||||||
<div class="login">
|
<div class="login">
|
||||||
<div class="header">Login to ${this.name}</div>
|
<div class="login-header">
|
||||||
<dees-form>
|
<div class="header">Sign in</div>
|
||||||
<dees-input-text key="username" label="Username" required></dees-input-text>
|
<div class="subheader">Enter your credentials to access ${this.name}</div>
|
||||||
<dees-input-text key="password" label="Password" isPasswordBool required></dees-input-text>
|
</div>
|
||||||
<dees-form-submit>Login</dees-form-submit>
|
<div class="login-card">
|
||||||
</dees-form>
|
<dees-form>
|
||||||
|
<dees-input-text key="username" label="Username" required></dees-input-text>
|
||||||
|
<dees-input-text key="password" label="Password" isPasswordBool required></dees-input-text>
|
||||||
|
<dees-form-submit>Sign in</dees-form-submit>
|
||||||
|
</dees-form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="slotContainer">
|
<div class="slotContainer">
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ export class DeesTerminal extends DeesElement {
|
|||||||
@property()
|
@property()
|
||||||
accessor environment: {[key: string]: string} = {};
|
accessor environment: {[key: string]: string} = {};
|
||||||
|
|
||||||
|
@property()
|
||||||
|
accessor background: string = '#000000';
|
||||||
|
|
||||||
// exposing webcontainer
|
// exposing webcontainer
|
||||||
private webcontainerDeferred = new domtools.plugins.smartpromise.Deferred<webcontainer.WebContainer>();
|
private webcontainerDeferred = new domtools.plugins.smartpromise.Deferred<webcontainer.WebContainer>();
|
||||||
public webcontainerPromise = this.webcontainerDeferred.promise;
|
public webcontainerPromise = this.webcontainerDeferred.promise;
|
||||||
@@ -58,7 +61,7 @@ export class DeesTerminal extends DeesElement {
|
|||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
background: #000;
|
background: var(--dees-terminal-background, #000000);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -153,7 +156,7 @@ export class DeesTerminal extends DeesElement {
|
|||||||
|
|
||||||
.xterm .composition-view {
|
.xterm .composition-view {
|
||||||
/* TODO: Composition position got messed up somewhere */
|
/* TODO: Composition position got messed up somewhere */
|
||||||
background: #000;
|
background: var(--dees-terminal-background, #000000);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -167,7 +170,7 @@ export class DeesTerminal extends DeesElement {
|
|||||||
|
|
||||||
.xterm .xterm-viewport {
|
.xterm .xterm-viewport {
|
||||||
/* On OS X this is required in order for the scroll bar to appear fully opaque */
|
/* On OS X this is required in order for the scroll bar to appear fully opaque */
|
||||||
background-color: #000;
|
background-color: var(--dees-terminal-background, #000000);
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -261,11 +264,18 @@ export class DeesTerminal extends DeesElement {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const domtools = await this.domtoolsPromise;
|
const domtools = await this.domtoolsPromise;
|
||||||
super.firstUpdated(_changedProperties);
|
super.firstUpdated(_changedProperties);
|
||||||
|
|
||||||
|
// Sync CSS variable with background property
|
||||||
|
this.style.setProperty('--dees-terminal-background', this.background);
|
||||||
|
|
||||||
const container = this.shadowRoot.getElementById('container');
|
const container = this.shadowRoot.getElementById('container');
|
||||||
|
|
||||||
const term = new Terminal({
|
const term = new Terminal({
|
||||||
convertEol: true,
|
convertEol: true,
|
||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
|
theme: {
|
||||||
|
background: this.background,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
this.fitAddon = new FitAddon();
|
this.fitAddon = new FitAddon();
|
||||||
term.loadAddon(this.fitAddon);
|
term.loadAddon(this.fitAddon);
|
||||||
|
|||||||
252
ts_web/elements/interfaces/appconfig.ts
Normal file
252
ts_web/elements/interfaces/appconfig.ts
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
import type { TemplateResult } from '@design.estate/dees-element';
|
||||||
|
import type { IAppBarMenuItem } from './appbarmenuitem.js';
|
||||||
|
import type { ITab } from './tab.js';
|
||||||
|
import type { ISecondaryMenuGroup } from './secondarymenu.js';
|
||||||
|
import type { IMenuGroup } from './menugroup.js';
|
||||||
|
|
||||||
|
// Forward declaration for circular reference
|
||||||
|
export type TDeesAppuiBase = HTMLElement & {
|
||||||
|
setAppBarMenus: (menus: IAppBarMenuItem[]) => void;
|
||||||
|
updateAppBarMenu: (name: string, update: Partial<IAppBarMenuItem>) => void;
|
||||||
|
setBreadcrumbs: (breadcrumbs: string | string[]) => void;
|
||||||
|
setUser: (user: IAppUser | undefined) => void;
|
||||||
|
setProfileMenuItems: (items: IAppBarMenuItem[]) => void;
|
||||||
|
setSearchVisible: (visible: boolean) => void;
|
||||||
|
setWindowControlsVisible: (visible: boolean) => void;
|
||||||
|
setMainMenu: (config: IMainMenuConfig) => void;
|
||||||
|
updateMainMenuGroup: (groupName: string, update: Partial<IMenuGroup>) => void;
|
||||||
|
addMainMenuItem: (groupName: string, tab: ITab) => void;
|
||||||
|
removeMainMenuItem: (groupName: string, tabKey: string) => void;
|
||||||
|
setMainMenuSelection: (tabKey: string) => void;
|
||||||
|
setMainMenuCollapsed: (collapsed: boolean) => void;
|
||||||
|
setMainMenuBadge: (tabKey: string, badge: string | number) => void;
|
||||||
|
clearMainMenuBadge: (tabKey: string) => void;
|
||||||
|
setSecondaryMenu: (config: { heading?: string; groups: ISecondaryMenuGroup[] }) => void;
|
||||||
|
updateSecondaryMenuGroup: (groupName: string, update: Partial<ISecondaryMenuGroup>) => void;
|
||||||
|
addSecondaryMenuItem: (groupName: string, item: ISecondaryMenuGroup['items'][0]) => void;
|
||||||
|
setSecondaryMenuSelection: (itemKey: string) => void;
|
||||||
|
clearSecondaryMenu: () => void;
|
||||||
|
setContentTabs: (tabs: ITab[]) => void;
|
||||||
|
addContentTab: (tab: ITab) => void;
|
||||||
|
removeContentTab: (tabKey: string) => void;
|
||||||
|
selectContentTab: (tabKey: string) => void;
|
||||||
|
getSelectedContentTab: () => ITab | undefined;
|
||||||
|
activityLog: IActivityLogAPI;
|
||||||
|
navigateToView: (viewId: string, params?: Record<string, string>) => Promise<boolean>;
|
||||||
|
getCurrentView: () => IViewDefinition | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User configuration for the app bar
|
||||||
|
*/
|
||||||
|
export interface IAppUser {
|
||||||
|
name: string;
|
||||||
|
email?: string;
|
||||||
|
avatar?: string;
|
||||||
|
status?: 'online' | 'offline' | 'busy' | 'away';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity entry for the activity log
|
||||||
|
*/
|
||||||
|
export interface IActivityEntry {
|
||||||
|
/** Unique identifier (auto-generated if not provided) */
|
||||||
|
id?: string;
|
||||||
|
/** Timestamp (auto-set to now if not provided) */
|
||||||
|
timestamp?: Date;
|
||||||
|
/** Activity type for icon styling */
|
||||||
|
type: 'login' | 'logout' | 'view' | 'create' | 'update' | 'delete' | 'custom';
|
||||||
|
/** User who performed the action */
|
||||||
|
user: string;
|
||||||
|
/** Activity message */
|
||||||
|
message: string;
|
||||||
|
/** Optional custom icon (overrides type-based icon) */
|
||||||
|
iconName?: string;
|
||||||
|
/** Optional additional data */
|
||||||
|
data?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity log programmatic API
|
||||||
|
*/
|
||||||
|
export interface IActivityLogAPI {
|
||||||
|
/** Add a single activity entry */
|
||||||
|
add: (entry: IActivityEntry) => void;
|
||||||
|
/** Add multiple activity entries */
|
||||||
|
addMany: (entries: IActivityEntry[]) => void;
|
||||||
|
/** Clear all entries */
|
||||||
|
clear: () => void;
|
||||||
|
/** Get all entries */
|
||||||
|
getEntries: () => IActivityEntry[];
|
||||||
|
/** Filter entries */
|
||||||
|
filter: (criteria: { user?: string; type?: IActivityEntry['type'] }) => IActivityEntry[];
|
||||||
|
/** Search entries by message */
|
||||||
|
search: (query: string) => IActivityEntry[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View activation context passed to onActivate lifecycle hook
|
||||||
|
*/
|
||||||
|
export interface IViewActivationContext {
|
||||||
|
/** Reference to the DeesAppuiBase instance */
|
||||||
|
appui: TDeesAppuiBase;
|
||||||
|
/** The view ID being activated */
|
||||||
|
viewId: string;
|
||||||
|
/** Route parameters if any */
|
||||||
|
params?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View lifecycle hooks interface
|
||||||
|
* Views can implement these methods to receive lifecycle notifications
|
||||||
|
*/
|
||||||
|
export interface IViewLifecycle {
|
||||||
|
/** Called when view is activated (displayed) */
|
||||||
|
onActivate?: (context: IViewActivationContext) => void | Promise<void>;
|
||||||
|
/** Called when view is deactivated (hidden) */
|
||||||
|
onDeactivate?: () => void | Promise<void>;
|
||||||
|
/** Called before navigation away - return false or message to block */
|
||||||
|
canDeactivate?: () => boolean | string | Promise<boolean | string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View definition for the view registry
|
||||||
|
*/
|
||||||
|
export interface IViewDefinition {
|
||||||
|
/** Unique identifier for routing */
|
||||||
|
id: string;
|
||||||
|
/** Display name */
|
||||||
|
name: string;
|
||||||
|
/** Optional icon */
|
||||||
|
iconName?: string;
|
||||||
|
/**
|
||||||
|
* The view content - can be:
|
||||||
|
* - Tag name string (e.g., 'my-dashboard')
|
||||||
|
* - Element class constructor
|
||||||
|
* - Template function returning TemplateResult
|
||||||
|
* - Async function returning any of the above (for lazy loading)
|
||||||
|
*/
|
||||||
|
content:
|
||||||
|
| string
|
||||||
|
| (new () => HTMLElement)
|
||||||
|
| (() => TemplateResult)
|
||||||
|
| (() => Promise<string | (new () => HTMLElement) | (() => TemplateResult)>);
|
||||||
|
/** Secondary menu items specific to this view */
|
||||||
|
secondaryMenu?: ISecondaryMenuGroup[];
|
||||||
|
/** Content tabs specific to this view */
|
||||||
|
contentTabs?: ITab[];
|
||||||
|
/** Optional route path (defaults to id). Supports params like 'settings/:section' */
|
||||||
|
route?: string;
|
||||||
|
/** Badge to show on menu item */
|
||||||
|
badge?: string | number;
|
||||||
|
badgeVariant?: 'default' | 'success' | 'warning' | 'error';
|
||||||
|
/** Whether to cache this view instance (default: true) */
|
||||||
|
cache?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main menu section with view references
|
||||||
|
*/
|
||||||
|
export interface IMainMenuSection {
|
||||||
|
/** Section name (optional for ungrouped) */
|
||||||
|
name?: string;
|
||||||
|
/** Views in this section (by ID reference) */
|
||||||
|
views: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main menu configuration
|
||||||
|
*/
|
||||||
|
export interface IMainMenuConfig {
|
||||||
|
/** Logo icon */
|
||||||
|
logoIcon?: string;
|
||||||
|
/** Logo text */
|
||||||
|
logoText?: string;
|
||||||
|
/** Menu groups with tabs */
|
||||||
|
groups?: IMenuGroup[];
|
||||||
|
/** Menu sections with view references (alternative to groups) */
|
||||||
|
sections?: IMainMenuSection[];
|
||||||
|
/** Bottom pinned items (view IDs or tabs) */
|
||||||
|
bottomItems?: string[];
|
||||||
|
/** Bottom tabs */
|
||||||
|
bottomTabs?: ITab[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App bar configuration
|
||||||
|
*/
|
||||||
|
export interface IAppBarConfig {
|
||||||
|
menuItems?: IAppBarMenuItem[];
|
||||||
|
breadcrumbs?: string;
|
||||||
|
breadcrumbSeparator?: string;
|
||||||
|
showWindowControls?: boolean;
|
||||||
|
showSearch?: boolean;
|
||||||
|
user?: IAppUser;
|
||||||
|
profileMenuItems?: IAppBarMenuItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Branding configuration
|
||||||
|
*/
|
||||||
|
export interface IBrandingConfig {
|
||||||
|
logoIcon?: string;
|
||||||
|
logoText?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activity log configuration
|
||||||
|
*/
|
||||||
|
export interface IActivityLogConfig {
|
||||||
|
/** Whether activity log is visible */
|
||||||
|
visible?: boolean;
|
||||||
|
/** Width of activity log panel */
|
||||||
|
width?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main unified configuration interface for dees-appui-base
|
||||||
|
*/
|
||||||
|
export interface IAppConfig {
|
||||||
|
/** Application branding */
|
||||||
|
branding?: IBrandingConfig;
|
||||||
|
|
||||||
|
/** App bar configuration */
|
||||||
|
appBar?: IAppBarConfig;
|
||||||
|
|
||||||
|
/** View definitions (the registry) */
|
||||||
|
views: IViewDefinition[];
|
||||||
|
|
||||||
|
/** Main menu structure */
|
||||||
|
mainMenu?: IMainMenuConfig;
|
||||||
|
|
||||||
|
/** Default view ID to show on startup */
|
||||||
|
defaultView?: string;
|
||||||
|
|
||||||
|
/** Activity log configuration */
|
||||||
|
activityLog?: IActivityLogConfig;
|
||||||
|
|
||||||
|
/** Event callbacks */
|
||||||
|
onViewChange?: (viewId: string, view: IViewDefinition) => void;
|
||||||
|
onSearch?: (query: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View change event detail
|
||||||
|
*/
|
||||||
|
export interface IViewChangeEvent {
|
||||||
|
viewId: string;
|
||||||
|
view: IViewDefinition;
|
||||||
|
previousView?: IViewDefinition;
|
||||||
|
params?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View lifecycle event (for rxjs Subject)
|
||||||
|
*/
|
||||||
|
export interface IViewLifecycleEvent {
|
||||||
|
type: 'activated' | 'deactivated' | 'loading' | 'loaded' | 'loadError';
|
||||||
|
viewId: string;
|
||||||
|
element?: HTMLElement;
|
||||||
|
params?: Record<string, string>;
|
||||||
|
error?: unknown;
|
||||||
|
}
|
||||||
@@ -3,3 +3,4 @@ export * from './selectionoption.js';
|
|||||||
export * from './appbarmenuitem.js';
|
export * from './appbarmenuitem.js';
|
||||||
export * from './menugroup.js';
|
export * from './menugroup.js';
|
||||||
export * from './secondarymenu.js';
|
export * from './secondarymenu.js';
|
||||||
|
export * from './appconfig.js';
|
||||||
|
|||||||
@@ -2,4 +2,6 @@ export interface ITab {
|
|||||||
key: string;
|
key: string;
|
||||||
iconName?: string;
|
iconName?: string;
|
||||||
action: () => void;
|
action: () => void;
|
||||||
|
badge?: string | number;
|
||||||
|
badgeVariant?: 'default' | 'success' | 'warning' | 'error';
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user