Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c41268cd4e | |||
| 30ebc47eda | |||
| 3b137c43a8 | |||
| 87fb3d91c3 | |||
| 8d6bd20321 | |||
| d7f3594dd4 | |||
| 2a6457e192 | |||
| 979e1f7991 | |||
| bbb57f1b9f |
BIN
.playwright-mcp/both-actionbars-test.png
Normal file
|
After Width: | Height: | Size: 76 KiB |
BIN
.playwright-mcp/editor-actionbar-test.png
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
.playwright-mcp/editor-actionbar-visible.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
.playwright-mcp/editor-actionbar-working.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
BIN
.playwright-mcp/terminal-actionbar-resize-issue.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
.playwright-mcp/terminal-resize-fix-verification.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
.playwright-mcp/terminal-with-actionbar-fix.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
.playwright-mcp/workspace-actionbar-layout.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
.playwright-mcp/workspace-file-open.png
Normal file
|
After Width: | Height: | Size: 74 KiB |
35
changelog.md
@@ -1,5 +1,40 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-01-02 - 3.28.0 - feat(dees-appui)
|
||||||
|
Rename DeesAppuiBase to DeesAppui and migrate related API, exports, demos and docs
|
||||||
|
|
||||||
|
- Renamed public component/tag and TypeScript types: DeesAppuiBase -> DeesAppui and TDeesAppuiBase -> TDeesAppui; updated IViewActivationContext.appui type accordingly
|
||||||
|
- Moved/rewired view registry implementation from dees-appui-base to dees-appui and updated module exports
|
||||||
|
- Updated README and demo files to reference DeesAppui and new readme paths (removed dees-appui-base docs/demo)
|
||||||
|
- Replaced dependency/imports of '@webcontainer/api' with '@tempfix/webcontainer__api' (package.json and source imports)
|
||||||
|
- Changed tsconfig.json: skipLibCheck set from true to false
|
||||||
|
|
||||||
|
## 2026-01-01 - 3.27.1 - fix(dees-actionbar)
|
||||||
|
always render actionbar wrapper and delay adding visible class to ensure grid/opacity animations run reliably
|
||||||
|
|
||||||
|
- Always render the actionbar wrapper (.actionbar-item and .actionbar-content) instead of returning early so grid-template-rows and opacity transitions can animate.
|
||||||
|
- Use optional chaining for current bar access (bar?.type, bar?.timeout) to avoid runtime errors when no bar is present.
|
||||||
|
- Adjust styles and structure: set :host display:block; move background/border to .actionbar-item; add .actionbar-content with min-height/opacity and transitions.
|
||||||
|
- Make processQueue asynchronous and await updateComplete, then add the 'visible' class inside requestAnimationFrame so the CSS transition is triggered after render.
|
||||||
|
|
||||||
|
## 2026-01-01 - 3.27.0 - feat(services)
|
||||||
|
introduce DeesServiceLibLoader to lazy-load heavy client libraries from CDN and update components to use it
|
||||||
|
|
||||||
|
- Add DeesServiceLibLoader singleton (ts_web/services/DeesServiceLibLoader.ts) to lazily load and cache libraries via jsDelivr ESM: xterm, xterm-addon-fit, highlight.js, ApexCharts, and Tiptap.
|
||||||
|
- Inject xterm CSS dynamically to avoid shipping xterm styles in the initial bundle.
|
||||||
|
- Expose helper methods preloadAll() and isLoaded(), and typed bundle interfaces (IXtermBundle, IXtermFitAddonBundle, ITiptapBundle).
|
||||||
|
- Update components to use runtime-loaded modules: dees-chart-area, dees-dataview-codebox, dees-input-richtext, wysiwyg code block, dees-workspace-terminal, terminal-tab-manager, dees-workspace-terminal-preview.
|
||||||
|
- TerminalTabManager now requires setXtermModules(...) before creating tabs and will throw if not initialized; workspace terminal now initializes and passes the loaded modules.
|
||||||
|
- Replace direct runtime imports of heavy libs with typed imports and runtime-loaded bundles to reduce initial bundle size and improve load performance.
|
||||||
|
|
||||||
|
## 2026-01-01 - 3.26.1 - fix(dees-actionbar)
|
||||||
|
animate actionbar hide using grid-template-rows and wait for animation before clearing state
|
||||||
|
|
||||||
|
- Switch host layout from block/max-height to grid using grid-template-rows for open/close transitions
|
||||||
|
- Add min-height: 0 to .actionbar-item to prevent flex children overflow and collapsing
|
||||||
|
- Introduce async hideCurrentBar() that removes 'visible', sets isVisible=false, waits 220ms then clears currentBar and currentResolve
|
||||||
|
- processQueue() now calls hideCurrentBar() asynchronously instead of clearing state immediately
|
||||||
|
|
||||||
## 2026-01-01 - 3.26.0 - feat(workspace)
|
## 2026-01-01 - 3.26.0 - feat(workspace)
|
||||||
add external file change detection, conflict resolution UI, and diff editor
|
add external file change detection, conflict resolution UI, and diff editor
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@design.estate/dees-catalog",
|
"name": "@design.estate/dees-catalog",
|
||||||
"version": "3.26.0",
|
"version": "3.28.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",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"@tiptap/extension-underline": "^2.23.0",
|
"@tiptap/extension-underline": "^2.23.0",
|
||||||
"@tiptap/starter-kit": "^2.23.0",
|
"@tiptap/starter-kit": "^2.23.0",
|
||||||
"@tsclass/tsclass": "^9.3.0",
|
"@tsclass/tsclass": "^9.3.0",
|
||||||
"@webcontainer/api": "1.6.1",
|
"@tempfix/webcontainer__api": "1.6.1",
|
||||||
"apexcharts": "^5.3.6",
|
"apexcharts": "^5.3.6",
|
||||||
"highlight.js": "11.11.1",
|
"highlight.js": "11.11.1",
|
||||||
"ibantools": "^4.5.1",
|
"ibantools": "^4.5.1",
|
||||||
|
|||||||
16
pnpm-lock.yaml
generated
@@ -35,6 +35,9 @@ importers:
|
|||||||
'@push.rocks/smartstring':
|
'@push.rocks/smartstring':
|
||||||
specifier: ^4.1.0
|
specifier: ^4.1.0
|
||||||
version: 4.1.0
|
version: 4.1.0
|
||||||
|
'@tempfix/webcontainer__api':
|
||||||
|
specifier: 1.6.1
|
||||||
|
version: 1.6.1
|
||||||
'@tiptap/core':
|
'@tiptap/core':
|
||||||
specifier: ^2.23.0
|
specifier: ^2.23.0
|
||||||
version: 2.27.1(@tiptap/pm@2.27.1)
|
version: 2.27.1(@tiptap/pm@2.27.1)
|
||||||
@@ -56,9 +59,6 @@ importers:
|
|||||||
'@tsclass/tsclass':
|
'@tsclass/tsclass':
|
||||||
specifier: ^9.3.0
|
specifier: ^9.3.0
|
||||||
version: 9.3.0
|
version: 9.3.0
|
||||||
'@webcontainer/api':
|
|
||||||
specifier: 1.6.1
|
|
||||||
version: 1.6.1
|
|
||||||
apexcharts:
|
apexcharts:
|
||||||
specifier: ^5.3.6
|
specifier: ^5.3.6
|
||||||
version: 5.3.6
|
version: 5.3.6
|
||||||
@@ -1450,6 +1450,9 @@ packages:
|
|||||||
'@tempfix/idb@8.0.3':
|
'@tempfix/idb@8.0.3':
|
||||||
resolution: {integrity: sha512-hPJQKO7+oAIY+pDNImrZ9QAINbz9KmwT+yO4iRVwdPanok2YKpaUxdJzIvCUwY0YgAawlvYdffbLvRLV5hbs2g==}
|
resolution: {integrity: sha512-hPJQKO7+oAIY+pDNImrZ9QAINbz9KmwT+yO4iRVwdPanok2YKpaUxdJzIvCUwY0YgAawlvYdffbLvRLV5hbs2g==}
|
||||||
|
|
||||||
|
'@tempfix/webcontainer__api@1.6.1':
|
||||||
|
resolution: {integrity: sha512-Hgn3cwy0vPzjrVBqeVnY0jNZLaOCW7d+dxBe7Jv9YGHAjJ8udUMS+KbTywSv5paAfld3A/RN/iolmMzOwZxLTA==}
|
||||||
|
|
||||||
'@tiptap/core@2.27.1':
|
'@tiptap/core@2.27.1':
|
||||||
resolution: {integrity: sha512-nkerkl8syHj44ZzAB7oA2GPmmZINKBKCa79FuNvmGJrJ4qyZwlkDzszud23YteFZEytbc87kVd/fP76ROS6sLg==}
|
resolution: {integrity: sha512-nkerkl8syHj44ZzAB7oA2GPmmZINKBKCa79FuNvmGJrJ4qyZwlkDzszud23YteFZEytbc87kVd/fP76ROS6sLg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1760,9 +1763,6 @@ packages:
|
|||||||
'@webcontainer/api@1.2.0':
|
'@webcontainer/api@1.2.0':
|
||||||
resolution: {integrity: sha512-tzoKBd4lLdhHy5GHFpUkl+ndoSba8JqmB7x0ZQFnWfjbcbQOvKQfxA8MEMUYhgqjWHnbrWdAfnBEHz5f5lYG5A==}
|
resolution: {integrity: sha512-tzoKBd4lLdhHy5GHFpUkl+ndoSba8JqmB7x0ZQFnWfjbcbQOvKQfxA8MEMUYhgqjWHnbrWdAfnBEHz5f5lYG5A==}
|
||||||
|
|
||||||
'@webcontainer/api@1.6.1':
|
|
||||||
resolution: {integrity: sha512-2RS2KiIw32BY1Icf6M1DvqSmcon9XICZCDgS29QJb2NmF12ZY2V5Ia+949hMKB3Wno+P/Y8W+sPP59PZeXSELg==}
|
|
||||||
|
|
||||||
'@yr/monotone-cubic-spline@1.0.3':
|
'@yr/monotone-cubic-spline@1.0.3':
|
||||||
resolution: {integrity: sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==}
|
resolution: {integrity: sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==}
|
||||||
|
|
||||||
@@ -6466,6 +6466,8 @@ snapshots:
|
|||||||
|
|
||||||
'@tempfix/idb@8.0.3': {}
|
'@tempfix/idb@8.0.3': {}
|
||||||
|
|
||||||
|
'@tempfix/webcontainer__api@1.6.1': {}
|
||||||
|
|
||||||
'@tiptap/core@2.27.1(@tiptap/pm@2.27.1)':
|
'@tiptap/core@2.27.1(@tiptap/pm@2.27.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tiptap/pm': 2.27.1
|
'@tiptap/pm': 2.27.1
|
||||||
@@ -6814,8 +6816,6 @@ snapshots:
|
|||||||
|
|
||||||
'@webcontainer/api@1.2.0': {}
|
'@webcontainer/api@1.2.0': {}
|
||||||
|
|
||||||
'@webcontainer/api@1.6.1': {}
|
|
||||||
|
|
||||||
'@yr/monotone-cubic-spline@1.0.3': {}
|
'@yr/monotone-cubic-spline@1.0.3': {}
|
||||||
|
|
||||||
accepts@1.3.8:
|
accepts@1.3.8:
|
||||||
|
|||||||
@@ -684,7 +684,7 @@ According to Lit's documentation (https://lit.dev/docs/components/decorators/#de
|
|||||||
|
|
||||||
## Enhanced AppUI API (2025-12-08)
|
## Enhanced AppUI API (2025-12-08)
|
||||||
|
|
||||||
The `dees-appui-base` component has been enhanced with a unified configuration API for building real-world applications.
|
The `dees-appui` component has been enhanced with a unified configuration API for building real-world applications.
|
||||||
|
|
||||||
### New Modules:
|
### New Modules:
|
||||||
|
|
||||||
@@ -734,7 +734,7 @@ interface IRoutingConfig {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### New Public Methods on DeesAppuiBase:
|
### New Public Methods on DeesAppui:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Configure with unified config
|
// Configure with unified config
|
||||||
@@ -774,7 +774,7 @@ const config: IAppConfig = {
|
|||||||
statePersistence: { enabled: true, storage: 'localStorage' },
|
statePersistence: { enabled: true, storage: 'localStorage' },
|
||||||
};
|
};
|
||||||
|
|
||||||
html`<dees-appui-base .config=${config}></dees-appui-base>`;
|
html`<dees-appui .config=${config}></dees-appui>`;
|
||||||
```
|
```
|
||||||
|
|
||||||
### Backward Compatibility:
|
### Backward Compatibility:
|
||||||
@@ -783,13 +783,13 @@ The existing property-based API still works:
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
html`
|
html`
|
||||||
<dees-appui-base
|
<dees-appui
|
||||||
.mainmenuGroups=${groups}
|
.mainmenuGroups=${groups}
|
||||||
.secondarymenuGroups=${secondaryGroups}
|
.secondarymenuGroups=${secondaryGroups}
|
||||||
@mainmenu-tab-select=${handler}
|
@mainmenu-tab-select=${handler}
|
||||||
>
|
>
|
||||||
<div slot="maincontent">...</div>
|
<div slot="maincontent">...</div>
|
||||||
</dees-appui-base>
|
</dees-appui>
|
||||||
`;
|
`;
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
14
readme.md
@@ -54,7 +54,7 @@ For developers working on this library, please refer to the [UI Components Playb
|
|||||||
|----------|------------|
|
|----------|------------|
|
||||||
| **Core UI** | [`DeesButton`](#deesbutton), [`DeesButtonExit`](#deesbuttonexit), [`DeesButtonGroup`](#deesbuttongroup), [`DeesBadge`](#deesbadge), [`DeesChips`](#deeschips), [`DeesHeading`](#deesheading), [`DeesHint`](#deeshint), [`DeesIcon`](#deesicon), [`DeesLabel`](#deeslabel), [`DeesPanel`](#deespanel), [`DeesSearchbar`](#deessearchbar), [`DeesSpinner`](#deesspinner), [`DeesToast`](#deestoast), [`DeesWindowcontrols`](#deeswindowcontrols) |
|
| **Core UI** | [`DeesButton`](#deesbutton), [`DeesButtonExit`](#deesbuttonexit), [`DeesButtonGroup`](#deesbuttongroup), [`DeesBadge`](#deesbadge), [`DeesChips`](#deeschips), [`DeesHeading`](#deesheading), [`DeesHint`](#deeshint), [`DeesIcon`](#deesicon), [`DeesLabel`](#deeslabel), [`DeesPanel`](#deespanel), [`DeesSearchbar`](#deessearchbar), [`DeesSpinner`](#deesspinner), [`DeesToast`](#deestoast), [`DeesWindowcontrols`](#deeswindowcontrols) |
|
||||||
| **Forms** | [`DeesForm`](#deesform), [`DeesInputText`](#deesinputtext), [`DeesInputCheckbox`](#deesinputcheckbox), [`DeesInputDropdown`](#deesinputdropdown), [`DeesInputRadiogroup`](#deesinputradiogroup), [`DeesInputFileupload`](#deesinputfileupload), [`DeesInputIban`](#deesinputiban), [`DeesInputPhone`](#deesinputphone), [`DeesInputQuantitySelector`](#deesinputquantityselector), [`DeesInputMultitoggle`](#deesinputmultitoggle), [`DeesInputTags`](#deesinputtags), [`DeesInputTypelist`](#deesinputtypelist), [`DeesInputRichtext`](#deesinputrichtext), [`DeesInputWysiwyg`](#deesinputwysiwyg), [`DeesInputDatepicker`](#deesinputdatepicker), [`DeesInputSearchselect`](#deesinputsearchselect), [`DeesFormSubmit`](#deesformsubmit) |
|
| **Forms** | [`DeesForm`](#deesform), [`DeesInputText`](#deesinputtext), [`DeesInputCheckbox`](#deesinputcheckbox), [`DeesInputDropdown`](#deesinputdropdown), [`DeesInputRadiogroup`](#deesinputradiogroup), [`DeesInputFileupload`](#deesinputfileupload), [`DeesInputIban`](#deesinputiban), [`DeesInputPhone`](#deesinputphone), [`DeesInputQuantitySelector`](#deesinputquantityselector), [`DeesInputMultitoggle`](#deesinputmultitoggle), [`DeesInputTags`](#deesinputtags), [`DeesInputTypelist`](#deesinputtypelist), [`DeesInputRichtext`](#deesinputrichtext), [`DeesInputWysiwyg`](#deesinputwysiwyg), [`DeesInputDatepicker`](#deesinputdatepicker), [`DeesInputSearchselect`](#deesinputsearchselect), [`DeesFormSubmit`](#deesformsubmit) |
|
||||||
| **Layout** | [`DeesAppuiBase`](#deesappuibase), [`DeesAppuiMainmenu`](#deesappuimainmenu), [`DeesAppuiSecondarymenu`](#deesappuisecondarymenu), [`DeesAppuiMaincontent`](#deesappuimaincontent), [`DeesAppuiAppbar`](#deesappuiappbar), [`DeesAppuiActivitylog`](#deesappuiactivitylog), [`DeesAppuiProfiledropdown`](#deesappuiprofiledropdown), [`DeesAppuiTabs`](#deesappuitabs), [`DeesMobileNavigation`](#deesmobilenavigation), [`DeesDashboardGrid`](#deesdashboardgrid) |
|
| **Layout** | [`DeesAppui`](#deesappui), [`DeesAppuiMainmenu`](#deesappuimainmenu), [`DeesAppuiSecondarymenu`](#deesappuisecondarymenu), [`DeesAppuiMaincontent`](#deesappuimaincontent), [`DeesAppuiAppbar`](#deesappuiappbar), [`DeesAppuiActivitylog`](#deesappuiactivitylog), [`DeesAppuiProfiledropdown`](#deesappuiprofiledropdown), [`DeesAppuiTabs`](#deesappuitabs), [`DeesMobileNavigation`](#deesmobilenavigation), [`DeesDashboardGrid`](#deesdashboardgrid) |
|
||||||
| **Data Display** | [`DeesTable`](#deestable), [`DeesDataviewCodebox`](#deesdataviewcodebox), [`DeesDataviewStatusobject`](#deesdataviewstatusobject), [`DeesPdf`](#deespdf), [`DeesStatsGrid`](#deesstatsgrid), [`DeesPagination`](#deespagination) |
|
| **Data Display** | [`DeesTable`](#deestable), [`DeesDataviewCodebox`](#deesdataviewcodebox), [`DeesDataviewStatusobject`](#deesdataviewstatusobject), [`DeesPdf`](#deespdf), [`DeesStatsGrid`](#deesstatsgrid), [`DeesPagination`](#deespagination) |
|
||||||
| **Visualization** | [`DeesChartArea`](#deeschartarea), [`DeesChartLog`](#deeschartlog) |
|
| **Visualization** | [`DeesChartArea`](#deeschartarea), [`DeesChartLog`](#deeschartlog) |
|
||||||
| **Dialogs & Overlays** | [`DeesModal`](#deesmodal), [`DeesContextmenu`](#deescontextmenu), [`DeesSpeechbubble`](#deesspeechbubble), [`DeesWindowlayer`](#deeswindowlayer) |
|
| **Dialogs & Overlays** | [`DeesModal`](#deesmodal), [`DeesContextmenu`](#deescontextmenu), [`DeesSpeechbubble`](#deesspeechbubble), [`DeesWindowlayer`](#deeswindowlayer) |
|
||||||
@@ -615,23 +615,23 @@ Submit button component specifically designed for `DeesForm`.
|
|||||||
|
|
||||||
### Layout Components
|
### Layout Components
|
||||||
|
|
||||||
#### `DeesAppuiBase`
|
#### `DeesAppui`
|
||||||
A comprehensive application shell component providing a complete UI framework with navigation, menus, activity logging, and view management.
|
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.
|
> **Full API Documentation**: See [ts_web/elements/00group-appui/dees-appui/readme.md](./ts_web/elements/00group-appui/dees-appui/readme.md) for complete documentation including all programmatic APIs, view lifecycle hooks, and TypeScript interfaces.
|
||||||
|
|
||||||
**Quick Start:**
|
**Quick Start:**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { html, DeesElement, customElement } from '@design.estate/dees-element';
|
import { html, DeesElement, customElement } from '@design.estate/dees-element';
|
||||||
import { DeesAppuiBase } from '@design.estate/dees-catalog';
|
import { DeesAppui } from '@design.estate/dees-catalog';
|
||||||
|
|
||||||
@customElement('my-app')
|
@customElement('my-app')
|
||||||
class MyApp extends DeesElement {
|
class MyApp extends DeesElement {
|
||||||
private appui: DeesAppuiBase;
|
private appui: DeesAppui;
|
||||||
|
|
||||||
async firstUpdated() {
|
async firstUpdated() {
|
||||||
this.appui = this.shadowRoot.querySelector('dees-appui-base');
|
this.appui = this.shadowRoot.querySelector('dees-appui');
|
||||||
|
|
||||||
// Configure with views and menu
|
// Configure with views and menu
|
||||||
this.appui.configure({
|
this.appui.configure({
|
||||||
@@ -648,7 +648,7 @@ class MyApp extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`<dees-appui-base></dees-appui-base>`;
|
return html`<dees-appui></dees-appui>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@design.estate/dees-catalog',
|
name: '@design.estate/dees-catalog',
|
||||||
version: '3.26.0',
|
version: '3.28.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,2 +0,0 @@
|
|||||||
export * from './dees-appui-base.js';
|
|
||||||
export * from './view.registry.js';
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { html, css, DeesElement, customElement, state } from '@design.estate/dees-element';
|
import { html, css, DeesElement, customElement, state } from '@design.estate/dees-element';
|
||||||
import type { DeesAppuiBase } from './dees-appui-base.js';
|
import type { DeesAppui } from './dees-appui.js';
|
||||||
import type { IAppConfig, IViewActivationContext } from '../../interfaces/appconfig.js';
|
import type { IAppConfig, IViewActivationContext } from '../../interfaces/appconfig.js';
|
||||||
import '@design.estate/dees-wcctools/demotools';
|
import '@design.estate/dees-wcctools/demotools';
|
||||||
|
|
||||||
@@ -197,7 +197,7 @@ class DemoSettingsView extends DeesElement {
|
|||||||
@state()
|
@state()
|
||||||
accessor hasChanges: boolean = false;
|
accessor hasChanges: boolean = false;
|
||||||
|
|
||||||
private appui: DeesAppuiBase;
|
private appui: DeesAppui;
|
||||||
|
|
||||||
onActivate(context: IViewActivationContext) {
|
onActivate(context: IViewActivationContext) {
|
||||||
this.appui = context.appui as any;
|
this.appui = context.appui as any;
|
||||||
@@ -619,7 +619,7 @@ export const demoFunc = () => {
|
|||||||
containerElement.className = 'demo-container';
|
containerElement.className = 'demo-container';
|
||||||
containerElement.style.cssText = 'position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden;';
|
containerElement.style.cssText = 'position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden;';
|
||||||
|
|
||||||
const appuiElement = document.createElement('dees-appui-base') as DeesAppuiBase;
|
const appuiElement = document.createElement('dees-appui') as DeesAppui;
|
||||||
containerElement.appendChild(appuiElement);
|
containerElement.appendChild(appuiElement);
|
||||||
|
|
||||||
// Initialize after element is connected
|
// Initialize after element is connected
|
||||||
@@ -15,7 +15,7 @@ import type { DeesAppuiMainmenu } from '../dees-appui-mainmenu/dees-appui-mainme
|
|||||||
import type { DeesAppuiSecondarymenu } from '../dees-appui-secondarymenu/dees-appui-secondarymenu.js';
|
import type { DeesAppuiSecondarymenu } from '../dees-appui-secondarymenu/dees-appui-secondarymenu.js';
|
||||||
import type { DeesAppuiMaincontent } from '../dees-appui-maincontent/dees-appui-maincontent.js';
|
import type { DeesAppuiMaincontent } from '../dees-appui-maincontent/dees-appui-maincontent.js';
|
||||||
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.demo.js';
|
||||||
import { themeDefaultStyles } from '../../00theme.js';
|
import { themeDefaultStyles } from '../../00theme.js';
|
||||||
|
|
||||||
// View registry for managing views
|
// View registry for managing views
|
||||||
@@ -30,12 +30,12 @@ import '../dees-appui-activitylog/dees-appui-activitylog.js';
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
'dees-appui-base': DeesAppuiBase;
|
'dees-appui': DeesAppui;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@customElement('dees-appui-base')
|
@customElement('dees-appui')
|
||||||
export class DeesAppuiBase extends DeesElement {
|
export class DeesAppui extends DeesElement {
|
||||||
public static demo = demoFunc;
|
public static demo = demoFunc;
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
@@ -278,7 +278,7 @@ export class DeesAppuiBase extends DeesElement {
|
|||||||
this.activitylogElement = this.shadowRoot!.querySelector('dees-appui-activitylog') as DeesAppuiActivitylog;
|
this.activitylogElement = this.shadowRoot!.querySelector('dees-appui-activitylog') as DeesAppuiActivitylog;
|
||||||
|
|
||||||
// Set appui reference in view registry for lifecycle context
|
// Set appui reference in view registry for lifecycle context
|
||||||
this.viewRegistry.setAppuiRef(this as unknown as interfaces.TDeesAppuiBase);
|
this.viewRegistry.setAppuiRef(this as unknown as interfaces.TDeesAppui);
|
||||||
}
|
}
|
||||||
|
|
||||||
async disconnectedCallback() {
|
async disconnectedCallback() {
|
||||||
2
ts_web/elements/00group-appui/dees-appui/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './dees-appui.js';
|
||||||
|
export * from './view.registry.js';
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# DeesAppuiBase
|
# DeesAppui
|
||||||
|
|
||||||
A comprehensive application shell component providing a complete UI framework with navigation, menus, activity logging, and view management.
|
A comprehensive application shell component providing a complete UI framework with navigation, menus, activity logging, and view management.
|
||||||
|
|
||||||
@@ -6,14 +6,14 @@ A comprehensive application shell component providing a complete UI framework wi
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { html, DeesElement, customElement } from '@design.estate/dees-element';
|
import { html, DeesElement, customElement } from '@design.estate/dees-element';
|
||||||
import { DeesAppuiBase } from '@design.estate/dees-catalog';
|
import { DeesAppui } from '@design.estate/dees-catalog';
|
||||||
|
|
||||||
@customElement('my-app')
|
@customElement('my-app')
|
||||||
class MyApp extends DeesElement {
|
class MyApp extends DeesElement {
|
||||||
private appui: DeesAppuiBase;
|
private appui: DeesAppui;
|
||||||
|
|
||||||
async firstUpdated() {
|
async firstUpdated() {
|
||||||
this.appui = this.shadowRoot.querySelector('dees-appui-base');
|
this.appui = this.shadowRoot.querySelector('dees-appui');
|
||||||
|
|
||||||
// Configure with views and menu
|
// Configure with views and menu
|
||||||
this.appui.configure({
|
this.appui.configure({
|
||||||
@@ -30,7 +30,7 @@ class MyApp extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`<dees-appui-base></dees-appui-base>`;
|
return html`<dees-appui></dees-appui>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -329,7 +329,7 @@ class MySettingsView extends DeesElement implements IViewLifecycle {
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
interface IViewActivationContext {
|
interface IViewActivationContext {
|
||||||
appui: DeesAppuiBase; // Reference to the app shell
|
appui: DeesAppui; // Reference to the app shell
|
||||||
viewId: string; // The view ID being activated
|
viewId: string; // The view ID being activated
|
||||||
params?: Record<string, string>; // Route parameters
|
params?: Record<string, string>; // Route parameters
|
||||||
}
|
}
|
||||||
@@ -421,14 +421,14 @@ appui.viewChanged$.subscribe((event) => {
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { html, DeesElement, customElement } from '@design.estate/dees-element';
|
import { html, DeesElement, customElement } from '@design.estate/dees-element';
|
||||||
import { DeesAppuiBase, IViewActivationContext } from '@design.estate/dees-catalog';
|
import { DeesAppui, IViewActivationContext } from '@design.estate/dees-catalog';
|
||||||
|
|
||||||
@customElement('my-app')
|
@customElement('my-app')
|
||||||
class MyApp extends DeesElement {
|
class MyApp extends DeesElement {
|
||||||
private appui: DeesAppuiBase;
|
private appui: DeesAppui;
|
||||||
|
|
||||||
async firstUpdated() {
|
async firstUpdated() {
|
||||||
this.appui = this.shadowRoot.querySelector('dees-appui-base');
|
this.appui = this.shadowRoot.querySelector('dees-appui');
|
||||||
|
|
||||||
this.appui.configure({
|
this.appui.configure({
|
||||||
branding: {
|
branding: {
|
||||||
@@ -494,14 +494,14 @@ class MyApp extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`<dees-appui-base></dees-appui-base>`;
|
return html`<dees-appui></dees-appui>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// View with lifecycle hooks
|
// View with lifecycle hooks
|
||||||
@customElement('crm-settings')
|
@customElement('crm-settings')
|
||||||
class CrmSettings extends DeesElement {
|
class CrmSettings extends DeesElement {
|
||||||
private appui: DeesAppuiBase;
|
private appui: DeesAppui;
|
||||||
|
|
||||||
onActivate(context: IViewActivationContext) {
|
onActivate(context: IViewActivationContext) {
|
||||||
this.appui = context.appui;
|
this.appui = context.appui;
|
||||||
@@ -3,7 +3,7 @@ import type {
|
|||||||
IViewDefinition,
|
IViewDefinition,
|
||||||
IViewActivationContext,
|
IViewActivationContext,
|
||||||
IViewLifecycle,
|
IViewLifecycle,
|
||||||
TDeesAppuiBase
|
TDeesAppui
|
||||||
} from '../../interfaces/appconfig.js';
|
} from '../../interfaces/appconfig.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,12 +18,12 @@ export class ViewRegistry {
|
|||||||
private views: Map<string, IViewDefinition> = new Map();
|
private views: Map<string, IViewDefinition> = new Map();
|
||||||
private instances: Map<string, HTMLElement> = new Map();
|
private instances: Map<string, HTMLElement> = new Map();
|
||||||
private currentViewId: string | null = null;
|
private currentViewId: string | null = null;
|
||||||
private appui: TDeesAppuiBase | null = null;
|
private appui: TDeesAppui | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the appui reference for view activation context
|
* Set the appui reference for view activation context
|
||||||
*/
|
*/
|
||||||
public setAppuiRef(appui: TDeesAppuiBase): void {
|
public setAppuiRef(appui: TDeesAppui): void {
|
||||||
this.appui = appui;
|
this.appui = appui;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// App UI Components
|
// App UI Components
|
||||||
export * from './dees-appui-activitylog/index.js';
|
export * from './dees-appui-activitylog/index.js';
|
||||||
export * from './dees-appui-appbar/index.js';
|
export * from './dees-appui-appbar/index.js';
|
||||||
export * from './dees-appui-base/index.js';
|
export * from './dees-appui/index.js';
|
||||||
export * from './dees-appui-maincontent/index.js';
|
export * from './dees-appui-maincontent/index.js';
|
||||||
export * from './dees-appui-mainmenu/index.js';
|
export * from './dees-appui-mainmenu/index.js';
|
||||||
export * from './dees-appui-secondarymenu/index.js';
|
export * from './dees-appui-secondarymenu/index.js';
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import { demoFunc } from './demo.js';
|
|||||||
import { chartAreaStyles } from './styles.js';
|
import { chartAreaStyles } from './styles.js';
|
||||||
import { renderChartArea } from './template.js';
|
import { renderChartArea } from './template.js';
|
||||||
|
|
||||||
import ApexCharts from 'apexcharts';
|
import type ApexCharts from 'apexcharts';
|
||||||
|
import { DeesServiceLibLoader } from '../../../services/index.js';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
@@ -151,6 +152,9 @@ export class DeesChartArea extends DeesElement {
|
|||||||
public async firstUpdated() {
|
public async firstUpdated() {
|
||||||
await this.domtoolsPromise;
|
await this.domtoolsPromise;
|
||||||
|
|
||||||
|
// Load ApexCharts from CDN
|
||||||
|
const ApexChartsLib = await DeesServiceLibLoader.getInstance().loadApexCharts();
|
||||||
|
|
||||||
// Wait for next animation frame to ensure layout is complete
|
// Wait for next animation frame to ensure layout is complete
|
||||||
await new Promise(resolve => requestAnimationFrame(resolve));
|
await new Promise(resolve => requestAnimationFrame(resolve));
|
||||||
|
|
||||||
@@ -353,7 +357,7 @@ export class DeesChartArea extends DeesElement {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.chart = new ApexCharts(this.shadowRoot.querySelector('.chartContainer'), options);
|
this.chart = new ApexChartsLib(this.shadowRoot.querySelector('.chartContainer'), options);
|
||||||
await this.chart.render();
|
await this.chart.render();
|
||||||
|
|
||||||
// Give the chart a moment to fully initialize before resizing
|
// Give the chart a moment to fully initialize before resizing
|
||||||
|
|||||||
@@ -10,12 +10,13 @@ import {
|
|||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
import { cssGeistFontFamily, cssMonoFontFamily } from '../../00fonts.js';
|
import { cssGeistFontFamily, cssMonoFontFamily } from '../../00fonts.js';
|
||||||
|
|
||||||
import hlight from 'highlight.js';
|
import type { HLJSApi } from 'highlight.js';
|
||||||
|
|
||||||
import * as smartstring from '@push.rocks/smartstring';
|
import * as smartstring from '@push.rocks/smartstring';
|
||||||
|
|
||||||
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 { DeesServiceLibLoader } from '../../../services/index.js';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
@@ -229,6 +230,7 @@ export class DeesDataviewCodebox extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private codeToDisplayStore = '';
|
private codeToDisplayStore = '';
|
||||||
|
private highlightJs: HLJSApi | null = null;
|
||||||
|
|
||||||
public async updated(_changedProperties) {
|
public async updated(_changedProperties) {
|
||||||
super.updated(_changedProperties);
|
super.updated(_changedProperties);
|
||||||
@@ -250,11 +252,17 @@ export class DeesDataviewCodebox extends DeesElement {
|
|||||||
this.codeToDisplay = this.codeToDisplayStore;
|
this.codeToDisplay = this.codeToDisplayStore;
|
||||||
}
|
}
|
||||||
await domtools.plugins.smartdelay.delayFor(0);
|
await domtools.plugins.smartdelay.delayFor(0);
|
||||||
|
|
||||||
|
// Load highlight.js from CDN if not already loaded
|
||||||
|
if (!this.highlightJs) {
|
||||||
|
this.highlightJs = await DeesServiceLibLoader.getInstance().loadHighlightJs();
|
||||||
|
}
|
||||||
|
|
||||||
const localCodeNode = this.shadowRoot.querySelector('code');
|
const localCodeNode = this.shadowRoot.querySelector('code');
|
||||||
const html = hlight.highlight(this.codeToDisplayStore, {
|
const highlightedHtml = this.highlightJs.highlight(this.codeToDisplayStore, {
|
||||||
language: this.progLang,
|
language: this.progLang,
|
||||||
ignoreIllegals: true,
|
ignoreIllegals: true,
|
||||||
});
|
});
|
||||||
localCodeNode.innerHTML = html.value;
|
localCodeNode.innerHTML = highlightedHtml.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,12 +14,8 @@ import {
|
|||||||
query,
|
query,
|
||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
import { Editor } from '@tiptap/core';
|
import type { Editor } from '@tiptap/core';
|
||||||
import StarterKit from '@tiptap/starter-kit';
|
import { DeesServiceLibLoader, type ITiptapBundle } from '../../../services/index.js';
|
||||||
import Underline from '@tiptap/extension-underline';
|
|
||||||
import TextAlign from '@tiptap/extension-text-align';
|
|
||||||
import Link from '@tiptap/extension-link';
|
|
||||||
import Typography from '@tiptap/extension-typography';
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
@@ -63,6 +59,7 @@ export class DeesInputRichtext extends DeesInputBase<string> {
|
|||||||
|
|
||||||
private editorElement: HTMLElement;
|
private editorElement: HTMLElement;
|
||||||
private linkInputElement: HTMLInputElement;
|
private linkInputElement: HTMLInputElement;
|
||||||
|
private tiptapBundle: ITiptapBundle | null = null;
|
||||||
|
|
||||||
public editor: Editor;
|
public editor: Editor;
|
||||||
|
|
||||||
@@ -233,13 +230,19 @@ export class DeesInputRichtext extends DeesInputBase<string> {
|
|||||||
|
|
||||||
public async firstUpdated() {
|
public async firstUpdated() {
|
||||||
await this.updateComplete;
|
await this.updateComplete;
|
||||||
|
|
||||||
|
// Load Tiptap from CDN
|
||||||
|
this.tiptapBundle = await DeesServiceLibLoader.getInstance().loadTiptap();
|
||||||
|
|
||||||
this.editorElement = this.shadowRoot.querySelector('.editor-content');
|
this.editorElement = this.shadowRoot.querySelector('.editor-content');
|
||||||
this.linkInputElement = this.shadowRoot.querySelector('.link-input input');
|
this.linkInputElement = this.shadowRoot.querySelector('.link-input input');
|
||||||
this.initializeEditor();
|
this.initializeEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeEditor(): void {
|
private initializeEditor(): void {
|
||||||
if (this.disabled) return;
|
if (this.disabled || !this.tiptapBundle) return;
|
||||||
|
|
||||||
|
const { Editor, StarterKit, Underline, TextAlign, Link, Typography } = this.tiptapBundle;
|
||||||
|
|
||||||
this.editor = new Editor({
|
this.editor = new Editor({
|
||||||
element: this.editorElement,
|
element: this.editorElement,
|
||||||
@@ -249,7 +252,7 @@ export class DeesInputRichtext extends DeesInputBase<string> {
|
|||||||
levels: [1, 2, 3],
|
levels: [1, 2, 3],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
Underline,
|
Underline.configure({}),
|
||||||
TextAlign.configure({
|
TextAlign.configure({
|
||||||
types: ['heading', 'paragraph'],
|
types: ['heading', 'paragraph'],
|
||||||
}),
|
}),
|
||||||
@@ -259,7 +262,7 @@ export class DeesInputRichtext extends DeesInputBase<string> {
|
|||||||
class: 'editor-link',
|
class: 'editor-link',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
Typography,
|
Typography.configure({}),
|
||||||
],
|
],
|
||||||
content: this.value || (this.placeholder ? `<p>${this.placeholder}</p>` : ''),
|
content: this.value || (this.placeholder ? `<p>${this.placeholder}</p>` : ''),
|
||||||
onUpdate: ({ editor }) => {
|
onUpdate: ({ editor }) => {
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import { BaseBlockHandler, type IBlockEventHandlers } from '../block.base.js';
|
|||||||
import type { IBlock } from '../../wysiwyg.types.js';
|
import type { IBlock } from '../../wysiwyg.types.js';
|
||||||
import { cssManager } from '@design.estate/dees-element';
|
import { cssManager } from '@design.estate/dees-element';
|
||||||
import { WysiwygSelection } from '../../wysiwyg.selection.js';
|
import { WysiwygSelection } from '../../wysiwyg.selection.js';
|
||||||
import hlight from 'highlight.js';
|
import type { HLJSApi } from 'highlight.js';
|
||||||
import { cssGeistFontFamily, cssMonoFontFamily } from '../../../../00fonts.js';
|
import { cssGeistFontFamily, cssMonoFontFamily } from '../../../../00fonts.js';
|
||||||
import { PROGRAMMING_LANGUAGES } from '../../wysiwyg.constants.js';
|
import { PROGRAMMING_LANGUAGES } from '../../wysiwyg.constants.js';
|
||||||
|
import { DeesServiceLibLoader } from '../../../../../services/index.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CodeBlockHandler with improved architecture
|
* CodeBlockHandler with improved architecture
|
||||||
@@ -20,6 +21,7 @@ export class CodeBlockHandler extends BaseBlockHandler {
|
|||||||
type = 'code';
|
type = 'code';
|
||||||
|
|
||||||
private highlightTimer: any = null;
|
private highlightTimer: any = null;
|
||||||
|
private highlightJs: HLJSApi | null = null;
|
||||||
|
|
||||||
render(block: IBlock, isSelected: boolean): string {
|
render(block: IBlock, isSelected: boolean): string {
|
||||||
const language = block.metadata?.language || 'typescript';
|
const language = block.metadata?.language || 'typescript';
|
||||||
@@ -306,10 +308,15 @@ export class CodeBlockHandler extends BaseBlockHandler {
|
|||||||
return linesBeforeCursor.length - 1; // 0-indexed
|
return linesBeforeCursor.length - 1; // 0-indexed
|
||||||
}
|
}
|
||||||
|
|
||||||
private applyHighlighting(element: HTMLElement, block: IBlock): void {
|
private async applyHighlighting(element: HTMLElement, block: IBlock): Promise<void> {
|
||||||
const editor = element.querySelector('.code-editor') as HTMLElement;
|
const editor = element.querySelector('.code-editor') as HTMLElement;
|
||||||
if (!editor) return;
|
if (!editor) return;
|
||||||
|
|
||||||
|
// Load highlight.js from CDN if not already loaded
|
||||||
|
if (!this.highlightJs) {
|
||||||
|
this.highlightJs = await DeesServiceLibLoader.getInstance().loadHighlightJs();
|
||||||
|
}
|
||||||
|
|
||||||
// Store cursor position
|
// Store cursor position
|
||||||
const cursorPos = this.getCursorPosition(element);
|
const cursorPos = this.getCursorPosition(element);
|
||||||
|
|
||||||
@@ -319,9 +326,9 @@ export class CodeBlockHandler extends BaseBlockHandler {
|
|||||||
|
|
||||||
// Apply highlighting
|
// Apply highlighting
|
||||||
try {
|
try {
|
||||||
const result = hlight.highlight(content, {
|
const result = this.highlightJs.highlight(content, {
|
||||||
language: language,
|
language: language,
|
||||||
ignoreIllegals: true
|
ignoreIllegals: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Only update if we have valid highlighted content
|
// Only update if we have valid highlighted content
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as webcontainer from '@webcontainer/api';
|
import * as webcontainer from '@tempfix/webcontainer__api';
|
||||||
import type { IExecutionEnvironment, IFileEntry, IFileWatcher, IProcessHandle } from '../interfaces/IExecutionEnvironment.js';
|
import type { IExecutionEnvironment, IFileEntry, IFileWatcher, IProcessHandle } from '../interfaces/IExecutionEnvironment.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ import {
|
|||||||
css,
|
css,
|
||||||
cssManager,
|
cssManager,
|
||||||
} from '@design.estate/dees-element';
|
} from '@design.estate/dees-element';
|
||||||
import { Terminal } from 'xterm';
|
import type { Terminal } from 'xterm';
|
||||||
import { FitAddon } from 'xterm-addon-fit';
|
import type { FitAddon } from 'xterm-addon-fit';
|
||||||
import { themeDefaultStyles } from '../../00theme.js';
|
import { themeDefaultStyles } from '../../00theme.js';
|
||||||
|
import { DeesServiceLibLoader } from '../../../services/index.js';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
@@ -305,8 +306,15 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
|
|||||||
const domtoolsInstance = await this.domtoolsPromise;
|
const domtoolsInstance = await this.domtoolsPromise;
|
||||||
const isBright = domtoolsInstance.themeManager.goBrightBoolean;
|
const isBright = domtoolsInstance.themeManager.goBrightBoolean;
|
||||||
|
|
||||||
// Create xterm terminal in read-only mode
|
// Load xterm from CDN
|
||||||
this.terminal = new Terminal({
|
const libLoader = DeesServiceLibLoader.getInstance();
|
||||||
|
const [xtermBundle, fitAddonBundle] = await Promise.all([
|
||||||
|
libLoader.loadXterm(),
|
||||||
|
libLoader.loadXtermFitAddon(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Create xterm terminal in read-only mode using CDN-loaded module
|
||||||
|
this.terminal = new xtermBundle.Terminal({
|
||||||
convertEol: true,
|
convertEol: true,
|
||||||
cursorBlink: false,
|
cursorBlink: false,
|
||||||
disableStdin: true,
|
disableStdin: true,
|
||||||
@@ -323,7 +331,7 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.fitAddon = new FitAddon();
|
this.fitAddon = new fitAddonBundle.FitAddon();
|
||||||
this.terminal.loadAddon(this.fitAddon);
|
this.terminal.loadAddon(this.fitAddon);
|
||||||
this.terminal.open(container);
|
this.terminal.open(container);
|
||||||
this.fitAddon.fit();
|
this.fitAddon.fit();
|
||||||
|
|||||||
@@ -10,18 +10,20 @@ import {
|
|||||||
} 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 { Terminal } from 'xterm';
|
import type { Terminal } from 'xterm';
|
||||||
import { FitAddon } from 'xterm-addon-fit';
|
|
||||||
import { themeDefaultStyles } from '../../00theme.js';
|
import { themeDefaultStyles } from '../../00theme.js';
|
||||||
import type { IExecutionEnvironment } from '../../00group-runtime/index.js';
|
import type { IExecutionEnvironment } from '../../00group-runtime/index.js';
|
||||||
import { WebContainerEnvironment } from '../../00group-runtime/index.js';
|
import { WebContainerEnvironment } from '../../00group-runtime/index.js';
|
||||||
import '../../dees-icon/dees-icon.js';
|
import '../../dees-icon/dees-icon.js';
|
||||||
|
import '../../dees-actionbar/dees-actionbar.js';
|
||||||
|
import type { DeesActionbar } from '../../dees-actionbar/dees-actionbar.js';
|
||||||
import { TerminalTabManager } from './terminal-tab-manager.js';
|
import { TerminalTabManager } from './terminal-tab-manager.js';
|
||||||
import type {
|
import type {
|
||||||
ITerminalTab,
|
ITerminalTab,
|
||||||
ICreateTerminalTabOptions,
|
ICreateTerminalTabOptions,
|
||||||
TTerminalTabType,
|
TTerminalTabType,
|
||||||
} from './interfaces.js';
|
} from './interfaces.js';
|
||||||
|
import { DeesServiceLibLoader } from '../../../services/index.js';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface HTMLElementTagNameMap {
|
interface HTMLElementTagNameMap {
|
||||||
@@ -79,6 +81,9 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
|||||||
private terminalThemeSubscription: any = null;
|
private terminalThemeSubscription: any = null;
|
||||||
private isBright: boolean = false;
|
private isBright: boolean = false;
|
||||||
|
|
||||||
|
// Actionbar reference for terminal-context notifications
|
||||||
|
private terminalActionbar: DeesActionbar | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Promise that resolves when the environment is ready.
|
* Promise that resolves when the environment is ready.
|
||||||
* @deprecated Use executionEnvironment directly
|
* @deprecated Use executionEnvironment directly
|
||||||
@@ -120,17 +125,21 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
|||||||
|
|
||||||
.terminal-content {
|
.terminal-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
position: relative;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: ${cssManager.bdTheme('#ffffff', '#000000')};
|
background: ${cssManager.bdTheme('#ffffff', '#000000')};
|
||||||
}
|
}
|
||||||
|
|
||||||
#active-terminal-container {
|
#active-terminal-container {
|
||||||
position: absolute;
|
flex: 1;
|
||||||
top: 20px;
|
position: relative;
|
||||||
left: 20px;
|
min-height: 0;
|
||||||
right: 20px;
|
margin: 20px;
|
||||||
bottom: 20px;
|
}
|
||||||
|
|
||||||
|
.terminal-content dees-actionbar {
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tab bar on the right side */
|
/* Tab bar on the right side */
|
||||||
@@ -426,6 +435,7 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
|||||||
<span>No terminal open</span>
|
<span>No terminal open</span>
|
||||||
</div>
|
</div>
|
||||||
`}
|
`}
|
||||||
|
<dees-actionbar></dees-actionbar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Vertical tab bar on the right -->
|
<!-- Vertical tab bar on the right -->
|
||||||
@@ -485,17 +495,31 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Load xterm from CDN
|
||||||
|
const libLoader = DeesServiceLibLoader.getInstance();
|
||||||
|
const [xtermBundle, fitAddonBundle] = await Promise.all([
|
||||||
|
libLoader.loadXterm(),
|
||||||
|
libLoader.loadXtermFitAddon(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Initialize tab manager with loaded modules
|
||||||
|
this.tabManager.setXtermModules(xtermBundle, fitAddonBundle);
|
||||||
|
|
||||||
// Create default shell tab
|
// Create default shell tab
|
||||||
await this.createShellTab();
|
await this.createShellTab();
|
||||||
}
|
}
|
||||||
|
|
||||||
async connectedCallback(): Promise<void> {
|
async connectedCallback(): Promise<void> {
|
||||||
await super.connectedCallback();
|
await super.connectedCallback();
|
||||||
this.resizeObserver.observe(this);
|
// ResizeObserver is set up in attachTerminalToContainer when the container exists
|
||||||
}
|
}
|
||||||
|
|
||||||
async disconnectedCallback(): Promise<void> {
|
async disconnectedCallback(): Promise<void> {
|
||||||
this.resizeObserver.unobserve(this);
|
// Unobserve the terminal container
|
||||||
|
const container = this.shadowRoot?.getElementById('active-terminal-container');
|
||||||
|
if (container) {
|
||||||
|
this.resizeObserver.unobserve(container);
|
||||||
|
}
|
||||||
if (this.terminalThemeSubscription) {
|
if (this.terminalThemeSubscription) {
|
||||||
this.terminalThemeSubscription.unsubscribe();
|
this.terminalThemeSubscription.unsubscribe();
|
||||||
this.terminalThemeSubscription = null;
|
this.terminalThemeSubscription = null;
|
||||||
@@ -558,6 +582,10 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
|||||||
const container = this.shadowRoot?.getElementById('active-terminal-container');
|
const container = this.shadowRoot?.getElementById('active-terminal-container');
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
|
// Observe container for resize (handles actionbar appearing/disappearing)
|
||||||
|
// ResizeObserver.observe() is idempotent - safe to call multiple times
|
||||||
|
this.resizeObserver.observe(container);
|
||||||
|
|
||||||
// Clear container
|
// Clear container
|
||||||
container.innerHTML = '';
|
container.innerHTML = '';
|
||||||
|
|
||||||
@@ -656,6 +684,36 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
|||||||
detail: { tabId, exitCode },
|
detail: { tabId, exitCode },
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Show actionbar to offer closing the tab (only if tab is closeable)
|
||||||
|
if (tab.closeable) {
|
||||||
|
this.showExitedTabActionbar(tabId, tab.label, exitCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show actionbar offering to close an exited tab
|
||||||
|
*/
|
||||||
|
private async showExitedTabActionbar(tabId: string, tabLabel: string, exitCode: number): Promise<void> {
|
||||||
|
const isSuccess = exitCode === 0;
|
||||||
|
const result = await this.showActionbar({
|
||||||
|
message: isSuccess
|
||||||
|
? `"${tabLabel}" completed. Close tab?`
|
||||||
|
: `"${tabLabel}" exited (code ${exitCode}). Close tab?`,
|
||||||
|
type: isSuccess ? 'info' : 'warning',
|
||||||
|
icon: isSuccess ? 'lucide:checkCircle' : 'lucide:alertTriangle',
|
||||||
|
actions: [
|
||||||
|
{ id: 'close', label: 'Close Tab', primary: true },
|
||||||
|
{ id: 'keep', label: 'Keep Open' },
|
||||||
|
],
|
||||||
|
timeout: { duration: 10000, defaultActionId: 'close' },
|
||||||
|
dismissible: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close tab if user clicked "Close Tab" or timeout triggered auto-close
|
||||||
|
if (result.actionId === 'close') {
|
||||||
|
this.closeTab(tabId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Public API ==========
|
// ========== Public API ==========
|
||||||
@@ -816,6 +874,19 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show an actionbar notification in the terminal panel context.
|
||||||
|
* Use this for terminal-related decisions (e.g., retry failed process, kill process, etc.)
|
||||||
|
*/
|
||||||
|
public async showActionbar(
|
||||||
|
options: Parameters<DeesActionbar['show']>[0]
|
||||||
|
): Promise<ReturnType<DeesActionbar['show']>> {
|
||||||
|
if (!this.terminalActionbar) {
|
||||||
|
this.terminalActionbar = this.shadowRoot?.querySelector('dees-actionbar') as DeesActionbar;
|
||||||
|
}
|
||||||
|
return this.terminalActionbar?.show(options);
|
||||||
|
}
|
||||||
|
|
||||||
// ========== Utility Methods ==========
|
// ========== Utility Methods ==========
|
||||||
|
|
||||||
public async waitForPrompt(term: Terminal, prompt: string): Promise<void> {
|
public async waitForPrompt(term: Terminal, prompt: string): Promise<void> {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Terminal } from 'xterm';
|
import type { Terminal, ITerminalOptions } from 'xterm';
|
||||||
import { FitAddon } from 'xterm-addon-fit';
|
import type { FitAddon } from 'xterm-addon-fit';
|
||||||
import type { ITerminalTab, ICreateTerminalTabOptions, TTerminalTabType } from './interfaces.js';
|
import type { ITerminalTab, ICreateTerminalTabOptions, TTerminalTabType } from './interfaces.js';
|
||||||
|
import type { IXtermBundle, IXtermFitAddonBundle } from '../../../services/index.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages terminal tabs lifecycle and state
|
* Manages terminal tabs lifecycle and state
|
||||||
@@ -8,6 +9,17 @@ import type { ITerminalTab, ICreateTerminalTabOptions, TTerminalTabType } from '
|
|||||||
export class TerminalTabManager {
|
export class TerminalTabManager {
|
||||||
private tabs: Map<string, ITerminalTab> = new Map();
|
private tabs: Map<string, ITerminalTab> = new Map();
|
||||||
private tabCounter: number = 0;
|
private tabCounter: number = 0;
|
||||||
|
private xtermBundle: IXtermBundle | null = null;
|
||||||
|
private xtermFitAddonBundle: IXtermFitAddonBundle | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the manager with loaded xterm modules.
|
||||||
|
* Must be called before creating tabs.
|
||||||
|
*/
|
||||||
|
public setXtermModules(xtermBundle: IXtermBundle, fitAddonBundle: IXtermFitAddonBundle): void {
|
||||||
|
this.xtermBundle = xtermBundle;
|
||||||
|
this.xtermFitAddonBundle = fitAddonBundle;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate unique tab ID
|
* Generate unique tab ID
|
||||||
@@ -96,11 +108,15 @@ export class TerminalTabManager {
|
|||||||
* Create a new tab instance
|
* Create a new tab instance
|
||||||
*/
|
*/
|
||||||
createTab(options: ICreateTerminalTabOptions, isBright: boolean): ITerminalTab {
|
createTab(options: ICreateTerminalTabOptions, isBright: boolean): ITerminalTab {
|
||||||
|
if (!this.xtermBundle || !this.xtermFitAddonBundle) {
|
||||||
|
throw new Error('TerminalTabManager: xterm modules not initialized. Call setXtermModules() first.');
|
||||||
|
}
|
||||||
|
|
||||||
const id = this.generateTabId();
|
const id = this.generateTabId();
|
||||||
const type = options.type;
|
const type = options.type;
|
||||||
|
|
||||||
// Create xterm.js Terminal instance
|
// Create xterm.js Terminal instance using CDN-loaded module
|
||||||
const terminal = new Terminal({
|
const terminal = new this.xtermBundle.Terminal({
|
||||||
convertEol: true,
|
convertEol: true,
|
||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
theme: this.getTerminalTheme(isBright),
|
theme: this.getTerminalTheme(isBright),
|
||||||
@@ -109,8 +125,8 @@ export class TerminalTabManager {
|
|||||||
lineHeight: 1.2,
|
lineHeight: 1.2,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create FitAddon
|
// Create FitAddon using CDN-loaded module
|
||||||
const fitAddon = new FitAddon();
|
const fitAddon = new this.xtermFitAddonBundle.FitAddon();
|
||||||
terminal.loadAddon(fitAddon);
|
terminal.loadAddon(fitAddon);
|
||||||
|
|
||||||
const tab: ITerminalTab = {
|
const tab: ITerminalTab = {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import * as domtools from '@design.estate/dees-domtools';
|
|||||||
import { themeDefaultStyles } from '../../00theme.js';
|
import { themeDefaultStyles } from '../../00theme.js';
|
||||||
import type { IExecutionEnvironment, IFileWatcher } from '../../00group-runtime/index.js';
|
import type { IExecutionEnvironment, IFileWatcher } from '../../00group-runtime/index.js';
|
||||||
import { WebContainerEnvironment } from '../../00group-runtime/index.js';
|
import { WebContainerEnvironment } from '../../00group-runtime/index.js';
|
||||||
import type { FileSystemTree } from '@webcontainer/api';
|
import type { FileSystemTree } from '@tempfix/webcontainer__api';
|
||||||
import '../dees-workspace-monaco/dees-workspace-monaco.js';
|
import '../dees-workspace-monaco/dees-workspace-monaco.js';
|
||||||
import '../dees-workspace-filetree/dees-workspace-filetree.js';
|
import '../dees-workspace-filetree/dees-workspace-filetree.js';
|
||||||
import { DeesWorkspaceFiletree } from '../dees-workspace-filetree/dees-workspace-filetree.js';
|
import { DeesWorkspaceFiletree } from '../dees-workspace-filetree/dees-workspace-filetree.js';
|
||||||
@@ -182,7 +182,7 @@ testSmartPromise();
|
|||||||
await env.mount(fileTree);
|
await env.mount(fileTree);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Create container element for proper 100% height like dees-appui-base
|
// Create container element for proper 100% height like dees-appui
|
||||||
const containerElement = document.createElement('div');
|
const containerElement = document.createElement('div');
|
||||||
containerElement.style.cssText = 'position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden;';
|
containerElement.style.cssText = 'position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden;';
|
||||||
|
|
||||||
@@ -957,6 +957,7 @@ testSmartPromise();
|
|||||||
></dees-workspace-monaco>
|
></dees-workspace-monaco>
|
||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
|
<dees-actionbar></dees-actionbar>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Horizontal resize handle for terminal -->
|
<!-- Horizontal resize handle for terminal -->
|
||||||
@@ -1021,9 +1022,6 @@ testSmartPromise();
|
|||||||
.executionEnvironment=${this.executionEnvironment}
|
.executionEnvironment=${this.executionEnvironment}
|
||||||
@run-process=${this.handleRunProcess}
|
@run-process=${this.handleRunProcess}
|
||||||
></dees-workspace-bottombar>
|
></dees-workspace-bottombar>
|
||||||
|
|
||||||
<!-- Action Bar for notifications -->
|
|
||||||
<dees-actionbar></dees-actionbar>
|
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -1056,9 +1054,6 @@ testSmartPromise();
|
|||||||
this.currentFileTreeWidth = this.fileTreeWidth;
|
this.currentFileTreeWidth = this.fileTreeWidth;
|
||||||
this.currentTerminalHeight = this.terminalHeight;
|
this.currentTerminalHeight = this.terminalHeight;
|
||||||
|
|
||||||
// Get actionbar reference for file change notifications
|
|
||||||
this.actionbarElement = this.shadowRoot?.querySelector('dees-actionbar') as DeesActionbar;
|
|
||||||
|
|
||||||
if (this.executionEnvironment) {
|
if (this.executionEnvironment) {
|
||||||
await this.initializeWorkspace();
|
await this.initializeWorkspace();
|
||||||
}
|
}
|
||||||
@@ -1068,6 +1063,11 @@ testSmartPromise();
|
|||||||
if (changedProperties.has('executionEnvironment') && this.executionEnvironment) {
|
if (changedProperties.has('executionEnvironment') && this.executionEnvironment) {
|
||||||
await this.initializeWorkspace();
|
await this.initializeWorkspace();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Capture actionbar reference when it becomes available (after initialization completes)
|
||||||
|
if (!this.actionbarElement) {
|
||||||
|
this.actionbarElement = this.shadowRoot?.querySelector('.editor-panel dees-actionbar') as DeesActionbar;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initializeWorkspace() {
|
private async initializeWorkspace() {
|
||||||
|
|||||||
@@ -129,21 +129,29 @@ export class DeesActionbar extends DeesElement {
|
|||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
overflow: hidden;
|
|
||||||
height: 0;
|
|
||||||
transition: height 0.2s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host(.visible) {
|
|
||||||
height: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.actionbar-item {
|
.actionbar-item {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: column;
|
grid-template-rows: 0fr;
|
||||||
|
transition: grid-template-rows 0.2s ease-out;
|
||||||
background: ${cssManager.bdTheme('hsl(0 0% 96%)', 'hsl(0 0% 12%)')};
|
background: ${cssManager.bdTheme('hsl(0 0% 96%)', 'hsl(0 0% 12%)')};
|
||||||
border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 88%)', 'hsl(0 0% 20%)')};
|
border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 88%)', 'hsl(0 0% 20%)')};
|
||||||
|
}
|
||||||
|
|
||||||
|
:host(.visible) .actionbar-item {
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actionbar-content {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
min-height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host(.visible) .actionbar-content {
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progress-bar {
|
.progress-bar {
|
||||||
@@ -305,16 +313,15 @@ export class DeesActionbar extends DeesElement {
|
|||||||
];
|
];
|
||||||
|
|
||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
if (!this.currentBar) {
|
|
||||||
return html``;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bar = this.currentBar;
|
const bar = this.currentBar;
|
||||||
const type = bar.type || 'info';
|
const type = bar?.type || 'info';
|
||||||
const hasTimeout = bar.timeout && this.timeRemaining > 0;
|
const hasTimeout = bar?.timeout && this.timeRemaining > 0;
|
||||||
|
|
||||||
|
// ALWAYS render wrapper - required for grid animation to work
|
||||||
return html`
|
return html`
|
||||||
<div class="actionbar-item">
|
<div class="actionbar-item">
|
||||||
|
<div class="actionbar-content">
|
||||||
|
${bar ? html`
|
||||||
${hasTimeout ? html`
|
${hasTimeout ? html`
|
||||||
<div class="progress-bar">
|
<div class="progress-bar">
|
||||||
<div
|
<div
|
||||||
@@ -347,6 +354,8 @@ export class DeesActionbar extends DeesElement {
|
|||||||
` : ''}
|
` : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
` : ''}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -416,12 +425,27 @@ export class DeesActionbar extends DeesElement {
|
|||||||
|
|
||||||
// ========== Private Methods ==========
|
// ========== Private Methods ==========
|
||||||
|
|
||||||
private processQueue(): void {
|
/**
|
||||||
if (this.queue.length === 0) {
|
* Hide the current actionbar with animation.
|
||||||
|
* Removes visible class first to trigger CSS transition, then clears content after animation.
|
||||||
|
*/
|
||||||
|
private async hideCurrentBar(): Promise<void> {
|
||||||
|
// Remove visible class to start close animation
|
||||||
|
this.classList.remove('visible');
|
||||||
|
this.isVisible = false;
|
||||||
|
|
||||||
|
// Wait for animation to complete (200ms transition + buffer)
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 220));
|
||||||
|
|
||||||
|
// Now safe to clear content
|
||||||
this.currentBar = null;
|
this.currentBar = null;
|
||||||
this.currentResolve = null;
|
this.currentResolve = null;
|
||||||
this.isVisible = false;
|
}
|
||||||
this.classList.remove('visible');
|
|
||||||
|
private async processQueue(): Promise<void> {
|
||||||
|
if (this.queue.length === 0) {
|
||||||
|
// Hide with animation - don't await, let it run async
|
||||||
|
this.hideCurrentBar();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,7 +453,12 @@ export class DeesActionbar extends DeesElement {
|
|||||||
this.currentBar = item.options;
|
this.currentBar = item.options;
|
||||||
this.currentResolve = item.resolve;
|
this.currentResolve = item.resolve;
|
||||||
this.isVisible = true;
|
this.isVisible = true;
|
||||||
|
|
||||||
|
// Wait for Lit render, then add class on next frame to trigger animation
|
||||||
|
await this.updateComplete;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
this.classList.add('visible');
|
this.classList.add('visible');
|
||||||
|
});
|
||||||
|
|
||||||
// Setup timeout if configured
|
// Setup timeout if configured
|
||||||
if (item.options.timeout) {
|
if (item.options.timeout) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { IMenuItem } from './tab.js';
|
|||||||
import type { IMenuGroup } from './menugroup.js';
|
import type { IMenuGroup } from './menugroup.js';
|
||||||
|
|
||||||
// Forward declaration for circular reference
|
// Forward declaration for circular reference
|
||||||
export type TDeesAppuiBase = HTMLElement & {
|
export type TDeesAppui = HTMLElement & {
|
||||||
setAppBarMenus: (menus: IAppBarMenuItem[]) => void;
|
setAppBarMenus: (menus: IAppBarMenuItem[]) => void;
|
||||||
updateAppBarMenu: (name: string, update: Partial<IAppBarMenuItem>) => void;
|
updateAppBarMenu: (name: string, update: Partial<IAppBarMenuItem>) => void;
|
||||||
setBreadcrumbs: (breadcrumbs: string | string[]) => void;
|
setBreadcrumbs: (breadcrumbs: string | string[]) => void;
|
||||||
@@ -92,8 +92,8 @@ export interface IActivityLogAPI {
|
|||||||
* View activation context passed to onActivate lifecycle hook
|
* View activation context passed to onActivate lifecycle hook
|
||||||
*/
|
*/
|
||||||
export interface IViewActivationContext {
|
export interface IViewActivationContext {
|
||||||
/** Reference to the DeesAppuiBase instance */
|
/** Reference to the DeesAppui instance */
|
||||||
appui: TDeesAppuiBase;
|
appui: TDeesAppui;
|
||||||
/** The view ID being activated */
|
/** The view ID being activated */
|
||||||
viewId: string;
|
viewId: string;
|
||||||
/** Route parameters if any */
|
/** Route parameters if any */
|
||||||
@@ -208,7 +208,7 @@ export interface IActivityLogConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main unified configuration interface for dees-appui-base
|
* Main unified configuration interface for dees-appui
|
||||||
*/
|
*/
|
||||||
export interface IAppConfig {
|
export interface IAppConfig {
|
||||||
/** Application branding */
|
/** Application branding */
|
||||||
|
|||||||
285
ts_web/services/DeesServiceLibLoader.ts
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
import { CDN_BASE, CDN_VERSIONS } from './versions.js';
|
||||||
|
|
||||||
|
// Type imports (no runtime overhead)
|
||||||
|
import type { Terminal, ITerminalOptions } from 'xterm';
|
||||||
|
import type { FitAddon } from 'xterm-addon-fit';
|
||||||
|
import type { HLJSApi } from 'highlight.js';
|
||||||
|
import type ApexChartsType from 'apexcharts';
|
||||||
|
import type { Editor, EditorOptions } from '@tiptap/core';
|
||||||
|
import type { StarterKitOptions } from '@tiptap/starter-kit';
|
||||||
|
import type { UnderlineOptions } from '@tiptap/extension-underline';
|
||||||
|
import type { TextAlignOptions } from '@tiptap/extension-text-align';
|
||||||
|
import type { LinkOptions } from '@tiptap/extension-link';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bundle type for xterm and its addons
|
||||||
|
*/
|
||||||
|
export interface IXtermBundle {
|
||||||
|
Terminal: typeof Terminal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bundle type for xterm-addon-fit
|
||||||
|
*/
|
||||||
|
export interface IXtermFitAddonBundle {
|
||||||
|
FitAddon: typeof FitAddon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bundle type for Tiptap editor and extensions
|
||||||
|
*/
|
||||||
|
export interface ITiptapBundle {
|
||||||
|
Editor: typeof Editor;
|
||||||
|
StarterKit: { configure: (options?: Partial<StarterKitOptions>) => any };
|
||||||
|
Underline: { configure: (options?: Partial<UnderlineOptions>) => any };
|
||||||
|
TextAlign: { configure: (options?: Partial<TextAlignOptions>) => any };
|
||||||
|
Link: { configure: (options?: Partial<LinkOptions>) => any };
|
||||||
|
Typography: { configure: (options?: any) => any };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Singleton service for lazy-loading heavy libraries from CDN.
|
||||||
|
*
|
||||||
|
* This reduces initial bundle size by loading libraries only when needed.
|
||||||
|
* Libraries are cached after first load to avoid duplicate fetches.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const libLoader = DeesServiceLibLoader.getInstance();
|
||||||
|
* const xterm = await libLoader.loadXterm();
|
||||||
|
* const terminal = new xterm.Terminal({ ... });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export class DeesServiceLibLoader {
|
||||||
|
private static instance: DeesServiceLibLoader;
|
||||||
|
|
||||||
|
// Cached library references
|
||||||
|
private xtermLib: IXtermBundle | null = null;
|
||||||
|
private xtermFitAddonLib: IXtermFitAddonBundle | null = null;
|
||||||
|
private highlightJsLib: HLJSApi | null = null;
|
||||||
|
private apexChartsLib: typeof ApexChartsType | null = null;
|
||||||
|
private tiptapLib: ITiptapBundle | null = null;
|
||||||
|
|
||||||
|
// Loading promises to prevent duplicate concurrent loads
|
||||||
|
private xtermLoadingPromise: Promise<IXtermBundle> | null = null;
|
||||||
|
private xtermFitAddonLoadingPromise: Promise<IXtermFitAddonBundle> | null = null;
|
||||||
|
private highlightJsLoadingPromise: Promise<HLJSApi> | null = null;
|
||||||
|
private apexChartsLoadingPromise: Promise<typeof ApexChartsType> | null = null;
|
||||||
|
private tiptapLoadingPromise: Promise<ITiptapBundle> | null = null;
|
||||||
|
|
||||||
|
private constructor() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the singleton instance of DeesServiceLibLoader
|
||||||
|
*/
|
||||||
|
public static getInstance(): DeesServiceLibLoader {
|
||||||
|
if (!DeesServiceLibLoader.instance) {
|
||||||
|
DeesServiceLibLoader.instance = new DeesServiceLibLoader();
|
||||||
|
}
|
||||||
|
return DeesServiceLibLoader.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load xterm terminal emulator from CDN
|
||||||
|
* @returns Promise resolving to xterm module with Terminal class
|
||||||
|
*/
|
||||||
|
public async loadXterm(): Promise<IXtermBundle> {
|
||||||
|
if (this.xtermLib) {
|
||||||
|
return this.xtermLib;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.xtermLoadingPromise) {
|
||||||
|
return this.xtermLoadingPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.xtermLoadingPromise = (async () => {
|
||||||
|
const url = `${CDN_BASE}/xterm@${CDN_VERSIONS.xterm}/+esm`;
|
||||||
|
const module = await import(/* @vite-ignore */ url);
|
||||||
|
|
||||||
|
// Also load and inject xterm CSS
|
||||||
|
await this.injectXtermStyles();
|
||||||
|
|
||||||
|
this.xtermLib = {
|
||||||
|
Terminal: module.Terminal,
|
||||||
|
};
|
||||||
|
return this.xtermLib;
|
||||||
|
})();
|
||||||
|
|
||||||
|
return this.xtermLoadingPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load xterm-addon-fit from CDN
|
||||||
|
* @returns Promise resolving to FitAddon class
|
||||||
|
*/
|
||||||
|
public async loadXtermFitAddon(): Promise<IXtermFitAddonBundle> {
|
||||||
|
if (this.xtermFitAddonLib) {
|
||||||
|
return this.xtermFitAddonLib;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.xtermFitAddonLoadingPromise) {
|
||||||
|
return this.xtermFitAddonLoadingPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.xtermFitAddonLoadingPromise = (async () => {
|
||||||
|
const url = `${CDN_BASE}/xterm-addon-fit@${CDN_VERSIONS.xtermAddonFit}/+esm`;
|
||||||
|
const module = await import(/* @vite-ignore */ url);
|
||||||
|
|
||||||
|
this.xtermFitAddonLib = {
|
||||||
|
FitAddon: module.FitAddon,
|
||||||
|
};
|
||||||
|
return this.xtermFitAddonLib;
|
||||||
|
})();
|
||||||
|
|
||||||
|
return this.xtermFitAddonLoadingPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inject xterm CSS styles into the document head
|
||||||
|
*/
|
||||||
|
private async injectXtermStyles(): Promise<void> {
|
||||||
|
const styleId = 'xterm-cdn-styles';
|
||||||
|
if (document.getElementById(styleId)) {
|
||||||
|
return; // Already injected
|
||||||
|
}
|
||||||
|
|
||||||
|
const cssUrl = `${CDN_BASE}/xterm@${CDN_VERSIONS.xterm}/css/xterm.css`;
|
||||||
|
const response = await fetch(cssUrl);
|
||||||
|
const cssText = await response.text();
|
||||||
|
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.id = styleId;
|
||||||
|
style.textContent = cssText;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load highlight.js syntax highlighter from CDN
|
||||||
|
* @returns Promise resolving to highlight.js API
|
||||||
|
*/
|
||||||
|
public async loadHighlightJs(): Promise<HLJSApi> {
|
||||||
|
if (this.highlightJsLib) {
|
||||||
|
return this.highlightJsLib;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.highlightJsLoadingPromise) {
|
||||||
|
return this.highlightJsLoadingPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.highlightJsLoadingPromise = (async () => {
|
||||||
|
const url = `${CDN_BASE}/highlight.js@${CDN_VERSIONS.highlightJs}/+esm`;
|
||||||
|
const module = await import(/* @vite-ignore */ url);
|
||||||
|
|
||||||
|
this.highlightJsLib = module.default;
|
||||||
|
return this.highlightJsLib;
|
||||||
|
})();
|
||||||
|
|
||||||
|
return this.highlightJsLoadingPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load ApexCharts charting library from CDN
|
||||||
|
* @returns Promise resolving to ApexCharts constructor
|
||||||
|
*/
|
||||||
|
public async loadApexCharts(): Promise<typeof ApexChartsType> {
|
||||||
|
if (this.apexChartsLib) {
|
||||||
|
return this.apexChartsLib;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.apexChartsLoadingPromise) {
|
||||||
|
return this.apexChartsLoadingPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.apexChartsLoadingPromise = (async () => {
|
||||||
|
const url = `${CDN_BASE}/apexcharts@${CDN_VERSIONS.apexcharts}/+esm`;
|
||||||
|
const module = await import(/* @vite-ignore */ url);
|
||||||
|
|
||||||
|
this.apexChartsLib = module.default;
|
||||||
|
return this.apexChartsLib;
|
||||||
|
})();
|
||||||
|
|
||||||
|
return this.apexChartsLoadingPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load Tiptap rich text editor and extensions from CDN
|
||||||
|
* @returns Promise resolving to Tiptap bundle with Editor and extensions
|
||||||
|
*/
|
||||||
|
public async loadTiptap(): Promise<ITiptapBundle> {
|
||||||
|
if (this.tiptapLib) {
|
||||||
|
return this.tiptapLib;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.tiptapLoadingPromise) {
|
||||||
|
return this.tiptapLoadingPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tiptapLoadingPromise = (async () => {
|
||||||
|
const version = CDN_VERSIONS.tiptap;
|
||||||
|
|
||||||
|
// Load all Tiptap modules in parallel
|
||||||
|
const [
|
||||||
|
coreModule,
|
||||||
|
starterKitModule,
|
||||||
|
underlineModule,
|
||||||
|
textAlignModule,
|
||||||
|
linkModule,
|
||||||
|
typographyModule,
|
||||||
|
] = await Promise.all([
|
||||||
|
import(/* @vite-ignore */ `${CDN_BASE}/@tiptap/core@${version}/+esm`),
|
||||||
|
import(/* @vite-ignore */ `${CDN_BASE}/@tiptap/starter-kit@${version}/+esm`),
|
||||||
|
import(/* @vite-ignore */ `${CDN_BASE}/@tiptap/extension-underline@${version}/+esm`),
|
||||||
|
import(/* @vite-ignore */ `${CDN_BASE}/@tiptap/extension-text-align@${version}/+esm`),
|
||||||
|
import(/* @vite-ignore */ `${CDN_BASE}/@tiptap/extension-link@${version}/+esm`),
|
||||||
|
import(/* @vite-ignore */ `${CDN_BASE}/@tiptap/extension-typography@${version}/+esm`),
|
||||||
|
]);
|
||||||
|
|
||||||
|
this.tiptapLib = {
|
||||||
|
Editor: coreModule.Editor,
|
||||||
|
StarterKit: starterKitModule.default || starterKitModule.StarterKit,
|
||||||
|
Underline: underlineModule.default || underlineModule.Underline,
|
||||||
|
TextAlign: textAlignModule.default || textAlignModule.TextAlign,
|
||||||
|
Link: linkModule.default || linkModule.Link,
|
||||||
|
Typography: typographyModule.default || typographyModule.Typography,
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.tiptapLib;
|
||||||
|
})();
|
||||||
|
|
||||||
|
return this.tiptapLoadingPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preload multiple libraries in parallel
|
||||||
|
* Useful for warming the cache before components are rendered
|
||||||
|
*/
|
||||||
|
public async preloadAll(): Promise<void> {
|
||||||
|
await Promise.all([
|
||||||
|
this.loadXterm(),
|
||||||
|
this.loadXtermFitAddon(),
|
||||||
|
this.loadHighlightJs(),
|
||||||
|
this.loadApexCharts(),
|
||||||
|
this.loadTiptap(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a specific library is already loaded
|
||||||
|
*/
|
||||||
|
public isLoaded(library: 'xterm' | 'xtermFitAddon' | 'highlightJs' | 'apexCharts' | 'tiptap'): boolean {
|
||||||
|
switch (library) {
|
||||||
|
case 'xterm':
|
||||||
|
return this.xtermLib !== null;
|
||||||
|
case 'xtermFitAddon':
|
||||||
|
return this.xtermFitAddonLib !== null;
|
||||||
|
case 'highlightJs':
|
||||||
|
return this.highlightJsLib !== null;
|
||||||
|
case 'apexCharts':
|
||||||
|
return this.apexChartsLib !== null;
|
||||||
|
case 'tiptap':
|
||||||
|
return this.tiptapLib !== null;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
ts_web/services/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export { DeesServiceLibLoader } from './DeesServiceLibLoader.js';
|
||||||
|
export type { IXtermBundle, IXtermFitAddonBundle, ITiptapBundle } from './DeesServiceLibLoader.js';
|
||||||
|
export { CDN_BASE, CDN_VERSIONS } from './versions.js';
|
||||||
17
ts_web/services/versions.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* CDN versions for lazy-loaded libraries.
|
||||||
|
* Keep these in sync with package.json for type compatibility.
|
||||||
|
*/
|
||||||
|
export const CDN_VERSIONS = {
|
||||||
|
xterm: '5.3.0',
|
||||||
|
xtermAddonFit: '0.8.0',
|
||||||
|
highlightJs: '11.11.1',
|
||||||
|
apexcharts: '5.3.6',
|
||||||
|
tiptap: '2.23.0',
|
||||||
|
fontawesome: '7.1.0',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base CDN URL for jsdelivr ESM imports
|
||||||
|
*/
|
||||||
|
export const CDN_BASE = 'https://cdn.jsdelivr.net/npm';
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "NodeNext",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"verbatimModuleSyntax": true,
|
"verbatimModuleSyntax": true,
|
||||||
"skipLibCheck": true
|
"skipLibCheck": false
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"dist_*/**/*.d.ts"
|
"dist_*/**/*.d.ts"
|
||||||
|
|||||||