feat(services): introduce DeesServiceLibLoader to lazy-load heavy client libraries from CDN and update components to use it
This commit is contained in:
@@ -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(() => {
|
||||
|
||||
@@ -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,8 +10,7 @@ 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';
|
||||
@@ -24,6 +23,7 @@ import type {
|
||||
ICreateTerminalTabOptions,
|
||||
TTerminalTabType,
|
||||
} from './interfaces.js';
|
||||
import { DeesServiceLibLoader } from '../../../services/index.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
@@ -495,6 +495,16 @@ 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();
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user