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
|
||||
|
||||
## 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)
|
||||
add external file change detection, conflict resolution UI, and diff editor
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@design.estate/dees-catalog",
|
||||
"version": "3.26.0",
|
||||
"version": "3.28.0",
|
||||
"private": false,
|
||||
"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",
|
||||
@@ -32,7 +32,7 @@
|
||||
"@tiptap/extension-underline": "^2.23.0",
|
||||
"@tiptap/starter-kit": "^2.23.0",
|
||||
"@tsclass/tsclass": "^9.3.0",
|
||||
"@webcontainer/api": "1.6.1",
|
||||
"@tempfix/webcontainer__api": "1.6.1",
|
||||
"apexcharts": "^5.3.6",
|
||||
"highlight.js": "11.11.1",
|
||||
"ibantools": "^4.5.1",
|
||||
|
||||
16
pnpm-lock.yaml
generated
@@ -35,6 +35,9 @@ importers:
|
||||
'@push.rocks/smartstring':
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0
|
||||
'@tempfix/webcontainer__api':
|
||||
specifier: 1.6.1
|
||||
version: 1.6.1
|
||||
'@tiptap/core':
|
||||
specifier: ^2.23.0
|
||||
version: 2.27.1(@tiptap/pm@2.27.1)
|
||||
@@ -56,9 +59,6 @@ importers:
|
||||
'@tsclass/tsclass':
|
||||
specifier: ^9.3.0
|
||||
version: 9.3.0
|
||||
'@webcontainer/api':
|
||||
specifier: 1.6.1
|
||||
version: 1.6.1
|
||||
apexcharts:
|
||||
specifier: ^5.3.6
|
||||
version: 5.3.6
|
||||
@@ -1450,6 +1450,9 @@ packages:
|
||||
'@tempfix/idb@8.0.3':
|
||||
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':
|
||||
resolution: {integrity: sha512-nkerkl8syHj44ZzAB7oA2GPmmZINKBKCa79FuNvmGJrJ4qyZwlkDzszud23YteFZEytbc87kVd/fP76ROS6sLg==}
|
||||
peerDependencies:
|
||||
@@ -1760,9 +1763,6 @@ packages:
|
||||
'@webcontainer/api@1.2.0':
|
||||
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':
|
||||
resolution: {integrity: sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==}
|
||||
|
||||
@@ -6466,6 +6466,8 @@ snapshots:
|
||||
|
||||
'@tempfix/idb@8.0.3': {}
|
||||
|
||||
'@tempfix/webcontainer__api@1.6.1': {}
|
||||
|
||||
'@tiptap/core@2.27.1(@tiptap/pm@2.27.1)':
|
||||
dependencies:
|
||||
'@tiptap/pm': 2.27.1
|
||||
@@ -6814,8 +6816,6 @@ snapshots:
|
||||
|
||||
'@webcontainer/api@1.2.0': {}
|
||||
|
||||
'@webcontainer/api@1.6.1': {}
|
||||
|
||||
'@yr/monotone-cubic-spline@1.0.3': {}
|
||||
|
||||
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)
|
||||
|
||||
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:
|
||||
|
||||
@@ -734,7 +734,7 @@ interface IRoutingConfig {
|
||||
}
|
||||
```
|
||||
|
||||
### New Public Methods on DeesAppuiBase:
|
||||
### New Public Methods on DeesAppui:
|
||||
|
||||
```typescript
|
||||
// Configure with unified config
|
||||
@@ -774,7 +774,7 @@ const config: IAppConfig = {
|
||||
statePersistence: { enabled: true, storage: 'localStorage' },
|
||||
};
|
||||
|
||||
html`<dees-appui-base .config=${config}></dees-appui-base>`;
|
||||
html`<dees-appui .config=${config}></dees-appui>`;
|
||||
```
|
||||
|
||||
### Backward Compatibility:
|
||||
@@ -783,13 +783,13 @@ The existing property-based API still works:
|
||||
|
||||
```typescript
|
||||
html`
|
||||
<dees-appui-base
|
||||
<dees-appui
|
||||
.mainmenuGroups=${groups}
|
||||
.secondarymenuGroups=${secondaryGroups}
|
||||
@mainmenu-tab-select=${handler}
|
||||
>
|
||||
<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) |
|
||||
| **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) |
|
||||
| **Visualization** | [`DeesChartArea`](#deeschartarea), [`DeesChartLog`](#deeschartlog) |
|
||||
| **Dialogs & Overlays** | [`DeesModal`](#deesmodal), [`DeesContextmenu`](#deescontextmenu), [`DeesSpeechbubble`](#deesspeechbubble), [`DeesWindowlayer`](#deeswindowlayer) |
|
||||
@@ -615,23 +615,23 @@ Submit button component specifically designed for `DeesForm`.
|
||||
|
||||
### Layout Components
|
||||
|
||||
#### `DeesAppuiBase`
|
||||
#### `DeesAppui`
|
||||
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:**
|
||||
|
||||
```typescript
|
||||
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')
|
||||
class MyApp extends DeesElement {
|
||||
private appui: DeesAppuiBase;
|
||||
private appui: DeesAppui;
|
||||
|
||||
async firstUpdated() {
|
||||
this.appui = this.shadowRoot.querySelector('dees-appui-base');
|
||||
this.appui = this.shadowRoot.querySelector('dees-appui');
|
||||
|
||||
// Configure with views and menu
|
||||
this.appui.configure({
|
||||
@@ -648,7 +648,7 @@ class MyApp extends DeesElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<dees-appui-base></dees-appui-base>`;
|
||||
return html`<dees-appui></dees-appui>`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
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.'
|
||||
}
|
||||
|
||||
@@ -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 type { DeesAppuiBase } from './dees-appui-base.js';
|
||||
import type { DeesAppui } from './dees-appui.js';
|
||||
import type { IAppConfig, IViewActivationContext } from '../../interfaces/appconfig.js';
|
||||
import '@design.estate/dees-wcctools/demotools';
|
||||
|
||||
@@ -197,7 +197,7 @@ class DemoSettingsView extends DeesElement {
|
||||
@state()
|
||||
accessor hasChanges: boolean = false;
|
||||
|
||||
private appui: DeesAppuiBase;
|
||||
private appui: DeesAppui;
|
||||
|
||||
onActivate(context: IViewActivationContext) {
|
||||
this.appui = context.appui as any;
|
||||
@@ -619,7 +619,7 @@ export const demoFunc = () => {
|
||||
containerElement.className = 'demo-container';
|
||||
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);
|
||||
|
||||
// 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 { DeesAppuiMaincontent } from '../dees-appui-maincontent/dees-appui-maincontent.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';
|
||||
|
||||
// View registry for managing views
|
||||
@@ -30,12 +30,12 @@ import '../dees-appui-activitylog/dees-appui-activitylog.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dees-appui-base': DeesAppuiBase;
|
||||
'dees-appui': DeesAppui;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dees-appui-base')
|
||||
export class DeesAppuiBase extends DeesElement {
|
||||
@customElement('dees-appui')
|
||||
export class DeesAppui extends DeesElement {
|
||||
public static demo = demoFunc;
|
||||
|
||||
// ==========================================
|
||||
@@ -278,7 +278,7 @@ export class DeesAppuiBase extends DeesElement {
|
||||
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);
|
||||
this.viewRegistry.setAppuiRef(this as unknown as interfaces.TDeesAppui);
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
@@ -6,14 +6,14 @@ A comprehensive application shell component providing a complete UI framework wi
|
||||
|
||||
```typescript
|
||||
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')
|
||||
class MyApp extends DeesElement {
|
||||
private appui: DeesAppuiBase;
|
||||
private appui: DeesAppui;
|
||||
|
||||
async firstUpdated() {
|
||||
this.appui = this.shadowRoot.querySelector('dees-appui-base');
|
||||
this.appui = this.shadowRoot.querySelector('dees-appui');
|
||||
|
||||
// Configure with views and menu
|
||||
this.appui.configure({
|
||||
@@ -30,7 +30,7 @@ class MyApp extends DeesElement {
|
||||
}
|
||||
|
||||
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
|
||||
interface IViewActivationContext {
|
||||
appui: DeesAppuiBase; // Reference to the app shell
|
||||
appui: DeesAppui; // Reference to the app shell
|
||||
viewId: string; // The view ID being activated
|
||||
params?: Record<string, string>; // Route parameters
|
||||
}
|
||||
@@ -421,14 +421,14 @@ appui.viewChanged$.subscribe((event) => {
|
||||
|
||||
```typescript
|
||||
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')
|
||||
class MyApp extends DeesElement {
|
||||
private appui: DeesAppuiBase;
|
||||
private appui: DeesAppui;
|
||||
|
||||
async firstUpdated() {
|
||||
this.appui = this.shadowRoot.querySelector('dees-appui-base');
|
||||
this.appui = this.shadowRoot.querySelector('dees-appui');
|
||||
|
||||
this.appui.configure({
|
||||
branding: {
|
||||
@@ -494,14 +494,14 @@ class MyApp extends DeesElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`<dees-appui-base></dees-appui-base>`;
|
||||
return html`<dees-appui></dees-appui>`;
|
||||
}
|
||||
}
|
||||
|
||||
// View with lifecycle hooks
|
||||
@customElement('crm-settings')
|
||||
class CrmSettings extends DeesElement {
|
||||
private appui: DeesAppuiBase;
|
||||
private appui: DeesAppui;
|
||||
|
||||
onActivate(context: IViewActivationContext) {
|
||||
this.appui = context.appui;
|
||||
@@ -3,7 +3,7 @@ import type {
|
||||
IViewDefinition,
|
||||
IViewActivationContext,
|
||||
IViewLifecycle,
|
||||
TDeesAppuiBase
|
||||
TDeesAppui
|
||||
} from '../../interfaces/appconfig.js';
|
||||
|
||||
/**
|
||||
@@ -18,12 +18,12 @@ 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;
|
||||
private appui: TDeesAppui | null = null;
|
||||
|
||||
/**
|
||||
* Set the appui reference for view activation context
|
||||
*/
|
||||
public setAppuiRef(appui: TDeesAppuiBase): void {
|
||||
public setAppuiRef(appui: TDeesAppui): void {
|
||||
this.appui = appui;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// App UI Components
|
||||
export * from './dees-appui-activitylog/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-mainmenu/index.js';
|
||||
export * from './dees-appui-secondarymenu/index.js';
|
||||
|
||||
@@ -11,7 +11,8 @@ import { demoFunc } from './demo.js';
|
||||
import { chartAreaStyles } from './styles.js';
|
||||
import { renderChartArea } from './template.js';
|
||||
|
||||
import ApexCharts from 'apexcharts';
|
||||
import type ApexCharts from 'apexcharts';
|
||||
import { DeesServiceLibLoader } from '../../../services/index.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
@@ -150,7 +151,10 @@ export class DeesChartArea extends DeesElement {
|
||||
|
||||
public async firstUpdated() {
|
||||
await this.domtoolsPromise;
|
||||
|
||||
|
||||
// Load ApexCharts from CDN
|
||||
const ApexChartsLib = await DeesServiceLibLoader.getInstance().loadApexCharts();
|
||||
|
||||
// Wait for next animation frame to ensure layout is complete
|
||||
await new Promise(resolve => requestAnimationFrame(resolve));
|
||||
|
||||
@@ -353,7 +357,7 @@ export class DeesChartArea extends DeesElement {
|
||||
};
|
||||
|
||||
try {
|
||||
this.chart = new ApexCharts(this.shadowRoot.querySelector('.chartContainer'), options);
|
||||
this.chart = new ApexChartsLib(this.shadowRoot.querySelector('.chartContainer'), options);
|
||||
await this.chart.render();
|
||||
|
||||
// Give the chart a moment to fully initialize before resizing
|
||||
|
||||
@@ -10,12 +10,13 @@ import {
|
||||
} from '@design.estate/dees-element';
|
||||
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 domtools from '@design.estate/dees-domtools';
|
||||
import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
|
||||
import { DeesServiceLibLoader } from '../../../services/index.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
@@ -229,6 +230,7 @@ export class DeesDataviewCodebox extends DeesElement {
|
||||
}
|
||||
|
||||
private codeToDisplayStore = '';
|
||||
private highlightJs: HLJSApi | null = null;
|
||||
|
||||
public async updated(_changedProperties) {
|
||||
super.updated(_changedProperties);
|
||||
@@ -250,11 +252,17 @@ export class DeesDataviewCodebox extends DeesElement {
|
||||
this.codeToDisplay = this.codeToDisplayStore;
|
||||
}
|
||||
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 html = hlight.highlight(this.codeToDisplayStore, {
|
||||
const highlightedHtml = this.highlightJs.highlight(this.codeToDisplayStore, {
|
||||
language: this.progLang,
|
||||
ignoreIllegals: true,
|
||||
});
|
||||
localCodeNode.innerHTML = html.value;
|
||||
localCodeNode.innerHTML = highlightedHtml.value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,12 +14,8 @@ import {
|
||||
query,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import { Editor } from '@tiptap/core';
|
||||
import StarterKit from '@tiptap/starter-kit';
|
||||
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';
|
||||
import type { Editor } from '@tiptap/core';
|
||||
import { DeesServiceLibLoader, type ITiptapBundle } from '../../../services/index.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
@@ -63,6 +59,7 @@ export class DeesInputRichtext extends DeesInputBase<string> {
|
||||
|
||||
private editorElement: HTMLElement;
|
||||
private linkInputElement: HTMLInputElement;
|
||||
private tiptapBundle: ITiptapBundle | null = null;
|
||||
|
||||
public editor: Editor;
|
||||
|
||||
@@ -233,13 +230,19 @@ export class DeesInputRichtext extends DeesInputBase<string> {
|
||||
|
||||
public async firstUpdated() {
|
||||
await this.updateComplete;
|
||||
|
||||
// Load Tiptap from CDN
|
||||
this.tiptapBundle = await DeesServiceLibLoader.getInstance().loadTiptap();
|
||||
|
||||
this.editorElement = this.shadowRoot.querySelector('.editor-content');
|
||||
this.linkInputElement = this.shadowRoot.querySelector('.link-input input');
|
||||
this.initializeEditor();
|
||||
}
|
||||
|
||||
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({
|
||||
element: this.editorElement,
|
||||
@@ -249,7 +252,7 @@ export class DeesInputRichtext extends DeesInputBase<string> {
|
||||
levels: [1, 2, 3],
|
||||
},
|
||||
}),
|
||||
Underline,
|
||||
Underline.configure({}),
|
||||
TextAlign.configure({
|
||||
types: ['heading', 'paragraph'],
|
||||
}),
|
||||
@@ -259,7 +262,7 @@ export class DeesInputRichtext extends DeesInputBase<string> {
|
||||
class: 'editor-link',
|
||||
},
|
||||
}),
|
||||
Typography,
|
||||
Typography.configure({}),
|
||||
],
|
||||
content: this.value || (this.placeholder ? `<p>${this.placeholder}</p>` : ''),
|
||||
onUpdate: ({ editor }) => {
|
||||
|
||||
@@ -2,9 +2,10 @@ import { BaseBlockHandler, type IBlockEventHandlers } from '../block.base.js';
|
||||
import type { IBlock } from '../../wysiwyg.types.js';
|
||||
import { cssManager } from '@design.estate/dees-element';
|
||||
import { WysiwygSelection } from '../../wysiwyg.selection.js';
|
||||
import hlight from 'highlight.js';
|
||||
import type { HLJSApi } from 'highlight.js';
|
||||
import { cssGeistFontFamily, cssMonoFontFamily } from '../../../../00fonts.js';
|
||||
import { PROGRAMMING_LANGUAGES } from '../../wysiwyg.constants.js';
|
||||
import { DeesServiceLibLoader } from '../../../../../services/index.js';
|
||||
|
||||
/**
|
||||
* CodeBlockHandler with improved architecture
|
||||
@@ -18,8 +19,9 @@ import { PROGRAMMING_LANGUAGES } from '../../wysiwyg.constants.js';
|
||||
*/
|
||||
export class CodeBlockHandler extends BaseBlockHandler {
|
||||
type = 'code';
|
||||
|
||||
|
||||
private highlightTimer: any = null;
|
||||
private highlightJs: HLJSApi | null = null;
|
||||
|
||||
render(block: IBlock, isSelected: boolean): string {
|
||||
const language = block.metadata?.language || 'typescript';
|
||||
@@ -306,28 +308,33 @@ export class CodeBlockHandler extends BaseBlockHandler {
|
||||
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;
|
||||
if (!editor) return;
|
||||
|
||||
|
||||
// Load highlight.js from CDN if not already loaded
|
||||
if (!this.highlightJs) {
|
||||
this.highlightJs = await DeesServiceLibLoader.getInstance().loadHighlightJs();
|
||||
}
|
||||
|
||||
// Store cursor position
|
||||
const cursorPos = this.getCursorPosition(element);
|
||||
|
||||
|
||||
// Get plain text content
|
||||
const content = editor.textContent || '';
|
||||
const language = block.metadata?.language || 'typescript';
|
||||
|
||||
|
||||
// Apply highlighting
|
||||
try {
|
||||
const result = hlight.highlight(content, {
|
||||
const result = this.highlightJs.highlight(content, {
|
||||
language: language,
|
||||
ignoreIllegals: true
|
||||
ignoreIllegals: true,
|
||||
});
|
||||
|
||||
|
||||
// Only update if we have valid highlighted content
|
||||
if (result.value) {
|
||||
editor.innerHTML = result.value;
|
||||
|
||||
|
||||
// Restore cursor position if editor is focused
|
||||
if (document.activeElement === editor && cursorPos !== null) {
|
||||
requestAnimationFrame(() => {
|
||||
|
||||
@@ -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';
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,9 +7,10 @@ import {
|
||||
css,
|
||||
cssManager,
|
||||
} from '@design.estate/dees-element';
|
||||
import { Terminal } from 'xterm';
|
||||
import { FitAddon } from 'xterm-addon-fit';
|
||||
import type { Terminal } from 'xterm';
|
||||
import type { FitAddon } from 'xterm-addon-fit';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
import { DeesServiceLibLoader } from '../../../services/index.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
@@ -305,8 +306,15 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
|
||||
const domtoolsInstance = await this.domtoolsPromise;
|
||||
const isBright = domtoolsInstance.themeManager.goBrightBoolean;
|
||||
|
||||
// Create xterm terminal in read-only mode
|
||||
this.terminal = new Terminal({
|
||||
// Load xterm from CDN
|
||||
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,
|
||||
cursorBlink: false,
|
||||
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.open(container);
|
||||
this.fitAddon.fit();
|
||||
|
||||
@@ -10,18 +10,20 @@ import {
|
||||
} from '@design.estate/dees-element';
|
||||
import * as domtools from '@design.estate/dees-domtools';
|
||||
|
||||
import { Terminal } from 'xterm';
|
||||
import { FitAddon } from 'xterm-addon-fit';
|
||||
import type { Terminal } from 'xterm';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
import type { IExecutionEnvironment } from '../../00group-runtime/index.js';
|
||||
import { WebContainerEnvironment } from '../../00group-runtime/index.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 type {
|
||||
ITerminalTab,
|
||||
ICreateTerminalTabOptions,
|
||||
TTerminalTabType,
|
||||
} from './interfaces.js';
|
||||
import { DeesServiceLibLoader } from '../../../services/index.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
@@ -79,6 +81,9 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
||||
private terminalThemeSubscription: any = null;
|
||||
private isBright: boolean = false;
|
||||
|
||||
// Actionbar reference for terminal-context notifications
|
||||
private terminalActionbar: DeesActionbar | null = null;
|
||||
|
||||
/**
|
||||
* Promise that resolves when the environment is ready.
|
||||
* @deprecated Use executionEnvironment directly
|
||||
@@ -120,17 +125,21 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
||||
|
||||
.terminal-content {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
background: ${cssManager.bdTheme('#ffffff', '#000000')};
|
||||
}
|
||||
|
||||
#active-terminal-container {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
flex: 1;
|
||||
position: relative;
|
||||
min-height: 0;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.terminal-content dees-actionbar {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Tab bar on the right side */
|
||||
@@ -426,6 +435,7 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
||||
<span>No terminal open</span>
|
||||
</div>
|
||||
`}
|
||||
<dees-actionbar></dees-actionbar>
|
||||
</div>
|
||||
|
||||
<!-- 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
|
||||
await this.createShellTab();
|
||||
}
|
||||
|
||||
async connectedCallback(): Promise<void> {
|
||||
await super.connectedCallback();
|
||||
this.resizeObserver.observe(this);
|
||||
// ResizeObserver is set up in attachTerminalToContainer when the container exists
|
||||
}
|
||||
|
||||
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) {
|
||||
this.terminalThemeSubscription.unsubscribe();
|
||||
this.terminalThemeSubscription = null;
|
||||
@@ -558,6 +582,10 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
||||
const container = this.shadowRoot?.getElementById('active-terminal-container');
|
||||
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
|
||||
container.innerHTML = '';
|
||||
|
||||
@@ -656,6 +684,36 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
||||
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 ==========
|
||||
@@ -816,6 +874,19 @@ export class DeesWorkspaceTerminal extends DeesElement {
|
||||
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 ==========
|
||||
|
||||
public async waitForPrompt(term: Terminal, prompt: string): Promise<void> {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Terminal } from 'xterm';
|
||||
import { FitAddon } from 'xterm-addon-fit';
|
||||
import type { Terminal, ITerminalOptions } from 'xterm';
|
||||
import type { FitAddon } from 'xterm-addon-fit';
|
||||
import type { ITerminalTab, ICreateTerminalTabOptions, TTerminalTabType } from './interfaces.js';
|
||||
import type { IXtermBundle, IXtermFitAddonBundle } from '../../../services/index.js';
|
||||
|
||||
/**
|
||||
* Manages terminal tabs lifecycle and state
|
||||
@@ -8,6 +9,17 @@ import type { ITerminalTab, ICreateTerminalTabOptions, TTerminalTabType } from '
|
||||
export class TerminalTabManager {
|
||||
private tabs: Map<string, ITerminalTab> = new Map();
|
||||
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
|
||||
@@ -96,11 +108,15 @@ export class TerminalTabManager {
|
||||
* Create a new tab instance
|
||||
*/
|
||||
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 type = options.type;
|
||||
|
||||
// Create xterm.js Terminal instance
|
||||
const terminal = new Terminal({
|
||||
// Create xterm.js Terminal instance using CDN-loaded module
|
||||
const terminal = new this.xtermBundle.Terminal({
|
||||
convertEol: true,
|
||||
cursorBlink: true,
|
||||
theme: this.getTerminalTheme(isBright),
|
||||
@@ -109,8 +125,8 @@ export class TerminalTabManager {
|
||||
lineHeight: 1.2,
|
||||
});
|
||||
|
||||
// Create FitAddon
|
||||
const fitAddon = new FitAddon();
|
||||
// Create FitAddon using CDN-loaded module
|
||||
const fitAddon = new this.xtermFitAddonBundle.FitAddon();
|
||||
terminal.loadAddon(fitAddon);
|
||||
|
||||
const tab: ITerminalTab = {
|
||||
|
||||
@@ -12,7 +12,7 @@ import * as domtools from '@design.estate/dees-domtools';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
import type { IExecutionEnvironment, IFileWatcher } 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-filetree/dees-workspace-filetree.js';
|
||||
import { DeesWorkspaceFiletree } from '../dees-workspace-filetree/dees-workspace-filetree.js';
|
||||
@@ -182,7 +182,7 @@ testSmartPromise();
|
||||
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');
|
||||
containerElement.style.cssText = 'position: absolute; top: 0; left: 0; height: 100%; width: 100%; overflow: hidden;';
|
||||
|
||||
@@ -957,6 +957,7 @@ testSmartPromise();
|
||||
></dees-workspace-monaco>
|
||||
`}
|
||||
</div>
|
||||
<dees-actionbar></dees-actionbar>
|
||||
</div>
|
||||
|
||||
<!-- Horizontal resize handle for terminal -->
|
||||
@@ -1021,9 +1022,6 @@ testSmartPromise();
|
||||
.executionEnvironment=${this.executionEnvironment}
|
||||
@run-process=${this.handleRunProcess}
|
||||
></dees-workspace-bottombar>
|
||||
|
||||
<!-- Action Bar for notifications -->
|
||||
<dees-actionbar></dees-actionbar>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -1056,9 +1054,6 @@ testSmartPromise();
|
||||
this.currentFileTreeWidth = this.fileTreeWidth;
|
||||
this.currentTerminalHeight = this.terminalHeight;
|
||||
|
||||
// Get actionbar reference for file change notifications
|
||||
this.actionbarElement = this.shadowRoot?.querySelector('dees-actionbar') as DeesActionbar;
|
||||
|
||||
if (this.executionEnvironment) {
|
||||
await this.initializeWorkspace();
|
||||
}
|
||||
@@ -1068,6 +1063,11 @@ testSmartPromise();
|
||||
if (changedProperties.has('executionEnvironment') && this.executionEnvironment) {
|
||||
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() {
|
||||
|
||||
@@ -129,21 +129,29 @@ export class DeesActionbar extends DeesElement {
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
transition: height 0.2s ease-out;
|
||||
}
|
||||
|
||||
:host(.visible) {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.actionbar-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: grid;
|
||||
grid-template-rows: 0fr;
|
||||
transition: grid-template-rows 0.2s ease-out;
|
||||
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%)')};
|
||||
}
|
||||
|
||||
:host(.visible) .actionbar-item {
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
|
||||
.actionbar-content {
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease-out;
|
||||
}
|
||||
|
||||
:host(.visible) .actionbar-content {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
@@ -305,47 +313,48 @@ export class DeesActionbar extends DeesElement {
|
||||
];
|
||||
|
||||
public render(): TemplateResult {
|
||||
if (!this.currentBar) {
|
||||
return html``;
|
||||
}
|
||||
|
||||
const bar = this.currentBar;
|
||||
const type = bar.type || 'info';
|
||||
const hasTimeout = bar.timeout && this.timeRemaining > 0;
|
||||
const type = bar?.type || 'info';
|
||||
const hasTimeout = bar?.timeout && this.timeRemaining > 0;
|
||||
|
||||
// ALWAYS render wrapper - required for grid animation to work
|
||||
return html`
|
||||
<div class="actionbar-item">
|
||||
${hasTimeout ? html`
|
||||
<div class="progress-bar">
|
||||
<div
|
||||
class="progress-bar-fill ${type}"
|
||||
style="width: ${this.progressPercent}%"
|
||||
></div>
|
||||
</div>
|
||||
` : ''}
|
||||
<div class="content">
|
||||
<div class="message-section">
|
||||
${bar.icon ? html`
|
||||
<dees-icon
|
||||
class="message-icon ${type}"
|
||||
.icon=${bar.icon}
|
||||
iconSize="16"
|
||||
></dees-icon>
|
||||
` : ''}
|
||||
<span class="message-text">${bar.message}</span>
|
||||
</div>
|
||||
<div class="actions-section">
|
||||
${bar.actions.map(action => this.renderActionButton(action, bar, hasTimeout))}
|
||||
${bar.dismissible ? html`
|
||||
<div
|
||||
class="dismiss-button"
|
||||
@click=${() => this.handleDismiss()}
|
||||
title="Dismiss"
|
||||
>
|
||||
<dees-icon .icon=${'lucide:x'} iconSize="14"></dees-icon>
|
||||
<div class="actionbar-content">
|
||||
${bar ? html`
|
||||
${hasTimeout ? html`
|
||||
<div class="progress-bar">
|
||||
<div
|
||||
class="progress-bar-fill ${type}"
|
||||
style="width: ${this.progressPercent}%"
|
||||
></div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="message-section">
|
||||
${bar.icon ? html`
|
||||
<dees-icon
|
||||
class="message-icon ${type}"
|
||||
.icon=${bar.icon}
|
||||
iconSize="16"
|
||||
></dees-icon>
|
||||
` : ''}
|
||||
<span class="message-text">${bar.message}</span>
|
||||
</div>
|
||||
<div class="actions-section">
|
||||
${bar.actions.map(action => this.renderActionButton(action, bar, hasTimeout))}
|
||||
${bar.dismissible ? html`
|
||||
<div
|
||||
class="dismiss-button"
|
||||
@click=${() => this.handleDismiss()}
|
||||
title="Dismiss"
|
||||
>
|
||||
<dees-icon .icon=${'lucide:x'} iconSize="14"></dees-icon>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -416,12 +425,27 @@ export class DeesActionbar extends DeesElement {
|
||||
|
||||
// ========== Private Methods ==========
|
||||
|
||||
private processQueue(): void {
|
||||
/**
|
||||
* 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.currentResolve = null;
|
||||
}
|
||||
|
||||
private async processQueue(): Promise<void> {
|
||||
if (this.queue.length === 0) {
|
||||
this.currentBar = null;
|
||||
this.currentResolve = null;
|
||||
this.isVisible = false;
|
||||
this.classList.remove('visible');
|
||||
// Hide with animation - don't await, let it run async
|
||||
this.hideCurrentBar();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -429,7 +453,12 @@ export class DeesActionbar extends DeesElement {
|
||||
this.currentBar = item.options;
|
||||
this.currentResolve = item.resolve;
|
||||
this.isVisible = true;
|
||||
this.classList.add('visible');
|
||||
|
||||
// Wait for Lit render, then add class on next frame to trigger animation
|
||||
await this.updateComplete;
|
||||
requestAnimationFrame(() => {
|
||||
this.classList.add('visible');
|
||||
});
|
||||
|
||||
// Setup timeout if configured
|
||||
if (item.options.timeout) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { IMenuItem } from './tab.js';
|
||||
import type { IMenuGroup } from './menugroup.js';
|
||||
|
||||
// Forward declaration for circular reference
|
||||
export type TDeesAppuiBase = HTMLElement & {
|
||||
export type TDeesAppui = HTMLElement & {
|
||||
setAppBarMenus: (menus: IAppBarMenuItem[]) => void;
|
||||
updateAppBarMenu: (name: string, update: Partial<IAppBarMenuItem>) => void;
|
||||
setBreadcrumbs: (breadcrumbs: string | string[]) => void;
|
||||
@@ -92,8 +92,8 @@ export interface IActivityLogAPI {
|
||||
* View activation context passed to onActivate lifecycle hook
|
||||
*/
|
||||
export interface IViewActivationContext {
|
||||
/** Reference to the DeesAppuiBase instance */
|
||||
appui: TDeesAppuiBase;
|
||||
/** Reference to the DeesAppui instance */
|
||||
appui: TDeesAppui;
|
||||
/** The view ID being activated */
|
||||
viewId: string;
|
||||
/** 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 {
|
||||
/** 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",
|
||||
"esModuleInterop": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"skipLibCheck": true
|
||||
"skipLibCheck": false
|
||||
},
|
||||
"exclude": [
|
||||
"dist_*/**/*.d.ts"
|
||||
|
||||