feat(dees-button-group): add new button group component with demo and styling

fix(dees-chart-area): improve real-time updates and chart element handling
fix(dees-chart-log): refactor demo to store log element reference
chore: update dependencies in package.json and pnpm-lock.yaml
This commit is contained in:
Juergen Kunz
2025-06-16 14:37:09 +00:00
parent 346abfa685
commit 48fbeb397d
10 changed files with 334 additions and 87 deletions

View File

@ -17,7 +17,7 @@
"dependencies": { "dependencies": {
"@design.estate/dees-domtools": "^2.1.1", "@design.estate/dees-domtools": "^2.1.1",
"@design.estate/dees-element": "^2.0.42", "@design.estate/dees-element": "^2.0.42",
"@design.estate/dees-wcctools": "^1.0.90", "@design.estate/dees-wcctools": "^1.0.92",
"@fortawesome/fontawesome-svg-core": "^6.7.2", "@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-brands-svg-icons": "^6.7.2", "@fortawesome/free-brands-svg-icons": "^6.7.2",
"@fortawesome/free-regular-svg-icons": "^6.7.2", "@fortawesome/free-regular-svg-icons": "^6.7.2",
@ -30,7 +30,7 @@
"apexcharts": "^4.7.0", "apexcharts": "^4.7.0",
"highlight.js": "11.11.1", "highlight.js": "11.11.1",
"ibantools": "^4.5.1", "ibantools": "^4.5.1",
"lucide": "^0.514.0", "lucide": "^0.515.0",
"monaco-editor": "^0.52.2", "monaco-editor": "^0.52.2",
"pdfjs-dist": "^4.10.38", "pdfjs-dist": "^4.10.38",
"xterm": "^5.3.0", "xterm": "^5.3.0",

20
pnpm-lock.yaml generated
View File

@ -15,8 +15,8 @@ importers:
specifier: ^2.0.42 specifier: ^2.0.42
version: 2.0.42 version: 2.0.42
'@design.estate/dees-wcctools': '@design.estate/dees-wcctools':
specifier: ^1.0.90 specifier: ^1.0.92
version: 1.0.90 version: 1.0.92
'@fortawesome/fontawesome-svg-core': '@fortawesome/fontawesome-svg-core':
specifier: ^6.7.2 specifier: ^6.7.2
version: 6.7.2 version: 6.7.2
@ -54,8 +54,8 @@ importers:
specifier: ^4.5.1 specifier: ^4.5.1
version: 4.5.1 version: 4.5.1
lucide: lucide:
specifier: ^0.514.0 specifier: ^0.515.0
version: 0.514.0 version: 0.515.0
monaco-editor: monaco-editor:
specifier: ^0.52.2 specifier: ^0.52.2
version: 0.52.2 version: 0.52.2
@ -305,8 +305,8 @@ packages:
'@design.estate/dees-element@2.0.42': '@design.estate/dees-element@2.0.42':
resolution: {integrity: sha512-1PzHP6q/PtSiu4P0nCxjSeHtRHn62zoSouMy8JFW2h29FT/CSDVaTUAUqYqnvwE/U98aLNivWTmerZitDF7kBQ==} resolution: {integrity: sha512-1PzHP6q/PtSiu4P0nCxjSeHtRHn62zoSouMy8JFW2h29FT/CSDVaTUAUqYqnvwE/U98aLNivWTmerZitDF7kBQ==}
'@design.estate/dees-wcctools@1.0.90': '@design.estate/dees-wcctools@1.0.92':
resolution: {integrity: sha512-EHYWHiOe+P261e9fBbOBmkD7lIsOpD+tu4VZQr20oc8vhsFjeUGJqYeBm/Ghwg+Gck/dto+K9zyJNIyQ642cEw==} resolution: {integrity: sha512-E4Hnxvvzy2ivJzPHzWL2dmJtBtAD+stnEG7uQ0usQM6NVnarIGPI9PflGSspM75nnA/HKi+lpsqRgp1DtbPqTQ==}
'@esbuild/aix-ppc64@0.24.2': '@esbuild/aix-ppc64@0.24.2':
resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==}
@ -3153,8 +3153,8 @@ packages:
resolution: {integrity: sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==} resolution: {integrity: sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==}
engines: {node: '>=16.14'} engines: {node: '>=16.14'}
lucide@0.514.0: lucide@0.515.0:
resolution: {integrity: sha512-GQ3Rzj1qFANBvTmhe8RM3vR491RGnSjHsivA5wSuEj5taLwH1Sv4N7n6ae3ENGsYRzdrkVXZo4ieUbD1WOXmoA==} resolution: {integrity: sha512-n7tFK3R1HdsxwM5rMOa6twAi/4RGZCysc7dmk7Cr2GiOUcnHX0+MX9xw3xynpbekXCgS4+o9HeIWhBl3rZHIAA==}
make-dir@3.1.0: make-dir@3.1.0:
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
@ -5176,7 +5176,7 @@ snapshots:
- supports-color - supports-color
- vue - vue
'@design.estate/dees-wcctools@1.0.90': '@design.estate/dees-wcctools@1.0.92':
dependencies: dependencies:
'@design.estate/dees-domtools': 2.3.2 '@design.estate/dees-domtools': 2.3.2
'@design.estate/dees-element': 2.0.42 '@design.estate/dees-element': 2.0.42
@ -8846,7 +8846,7 @@ snapshots:
lru-cache@8.0.5: {} lru-cache@8.0.5: {}
lucide@0.514.0: {} lucide@0.515.0: {}
make-dir@3.1.0: make-dir@3.1.0:
dependencies: dependencies:

View File

@ -25,6 +25,7 @@
- Methods: - Methods:
- `updateSeries()`: Update chart data - `updateSeries()`: Update chart data
- `appendData()`: Add new data points to existing series - `appendData()`: Add new data points to existing series
- Demo uses global reference to access chart element (window.__demoChartElement)
### dees-chart-log ### dees-chart-log
- Server log viewer component (not a chart despite the name) - Server log viewer component (not a chart despite the name)
@ -41,3 +42,20 @@
- Demo includes realistic server log simulation - Demo includes realistic server log simulation
- Note: In demos, buttons use `@clicked` event (not `@click`) - Note: In demos, buttons use `@clicked` event (not `@click`)
- Demo uses global reference to access log element (window.__demoLogElement) - Demo uses global reference to access log element (window.__demoLogElement)
## UI Components
### dees-button-group
- Groups multiple buttons together with a unified background
- Properties:
- `label`: Optional label text displayed before the buttons
- `direction`: 'horizontal' | 'vertical' layout
- Features:
- Light/dark theme support
- Flexible layout with proper spacing
- Works with all button types (normal, highlighted, success, danger)
- Use cases:
- View mode selectors
- Action grouping
- Navigation options
- Filter controls

View File

@ -0,0 +1,114 @@
import { html, css } from '@design.estate/dees-element';
export const demoFunc = () => {
return html`
<style>
${css`
.demoBox {
background: #000000;
padding: 40px;
min-height: 100vh;
box-sizing: border-box;
}
.demo-section {
margin-bottom: 32px;
}
.demo-title {
color: #fff;
font-size: 20px;
font-weight: 600;
margin-bottom: 16px;
font-family: 'Geist Sans', sans-serif;
}
.demo-description {
color: #999;
font-size: 14px;
margin-bottom: 24px;
font-family: 'Geist Sans', sans-serif;
}
`}
</style>
<div class="demoBox">
<div class="demo-section">
<h2 class="demo-title">Basic Button Groups</h2>
<p class="demo-description">Button groups without labels for simple grouping</p>
<dees-button-group>
<dees-button>Option 1</dees-button>
<dees-button>Option 2</dees-button>
<dees-button>Option 3</dees-button>
</dees-button-group>
</div>
<div class="demo-section">
<h2 class="demo-title">Labeled Button Groups</h2>
<p class="demo-description">Button groups with descriptive labels</p>
<dees-button-group label="View Mode:">
<dees-button type="highlighted">Grid</dees-button>
<dees-button>List</dees-button>
<dees-button>Cards</dees-button>
</dees-button-group>
</div>
<div class="demo-section">
<h2 class="demo-title">Multiple Groups</h2>
<p class="demo-description">Multiple button groups used together</p>
<div style="display: flex; gap: 16px; flex-wrap: wrap;">
<dees-button-group label="Dataset:">
<dees-button type="highlighted">System</dees-button>
<dees-button>Network</dees-button>
<dees-button>Sales</dees-button>
</dees-button-group>
<dees-button-group label="Time Range:">
<dees-button>1H</dees-button>
<dees-button type="highlighted">24H</dees-button>
<dees-button>7D</dees-button>
<dees-button>30D</dees-button>
</dees-button-group>
<dees-button-group label="Actions:">
<dees-button>Refresh</dees-button>
<dees-button>Export</dees-button>
</dees-button-group>
</div>
</div>
<div class="demo-section">
<h2 class="demo-title">Vertical Button Groups</h2>
<p class="demo-description">Button groups with vertical layout</p>
<div style="display: flex; gap: 24px;">
<dees-button-group direction="vertical" label="Navigation:">
<dees-button>Dashboard</dees-button>
<dees-button type="highlighted">Analytics</dees-button>
<dees-button>Reports</dees-button>
<dees-button>Settings</dees-button>
</dees-button-group>
<dees-button-group direction="vertical">
<dees-button>Add Item</dees-button>
<dees-button>Edit Item</dees-button>
<dees-button>Delete Item</dees-button>
</dees-button-group>
</div>
</div>
<div class="demo-section">
<h2 class="demo-title">Mixed Button Types</h2>
<p class="demo-description">Different button types within groups</p>
<dees-button-group label="Status:">
<dees-button type="success">Active</dees-button>
<dees-button>Pending</dees-button>
<dees-button type="danger">Inactive</dees-button>
</dees-button-group>
</div>
</div>
`;
};

View File

@ -0,0 +1,83 @@
import {
DeesElement,
css,
cssManager,
customElement,
html,
property,
type TemplateResult,
} from '@design.estate/dees-element';
import * as domtools from '@design.estate/dees-domtools';
import { demoFunc } from './dees-button-group.demo.js';
declare global {
interface HTMLElementTagNameMap {
'dees-button-group': DeesButtonGroup;
}
}
@customElement('dees-button-group')
export class DeesButtonGroup extends DeesElement {
public static demo = demoFunc;
@property()
public label: string = '';
@property()
public direction: 'horizontal' | 'vertical' = 'horizontal';
constructor() {
super();
domtools.elementBasic.setup();
}
public static styles = [
cssManager.defaultStyles,
css`
:host {
display: inline-block;
}
.button-group {
display: flex;
gap: 8px;
align-items: center;
padding: 8px;
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.05)', 'rgba(255, 255, 255, 0.05)')};
border-radius: 6px;
}
.button-group.vertical {
flex-direction: column;
align-items: stretch;
}
.label {
color: ${cssManager.bdTheme('#666', '#999')};
font-size: 12px;
font-family: 'Geist Sans', sans-serif;
margin-right: 8px;
white-space: nowrap;
}
.button-group.vertical .label {
margin-right: 0;
margin-bottom: 8px;
}
::slotted(*) {
margin: 0 !important;
}
`,
];
public render(): TemplateResult {
return html`
<div class="button-group ${this.direction}">
${this.label ? html`<span class="label">${this.label}</span>` : ''}
<slot></slot>
</div>
`;
}
}

View File

@ -1,19 +1,12 @@
import { html, css } from '@design.estate/dees-element'; import { html, css } from '@design.estate/dees-element';
import type { DeesChartArea } from './dees-chart-area.js'; import type { DeesChartArea } from './dees-chart-area.js';
import '@design.estate/dees-wcctools';
export const demoFunc = () => { export const demoFunc = () => {
let chartElement: DeesChartArea; let chartElement: DeesChartArea;
let intervalId: number; let intervalId: number;
let currentDataset = 'system'; let currentDataset = 'system';
// Get element reference after render
setTimeout(() => {
const charts = document.querySelectorAll('dees-chart-area');
if (charts.length > 0) {
chartElement = charts[charts.length - 1] as DeesChartArea;
}
}, 100);
// Y-axis formatters for different datasets // Y-axis formatters for different datasets
const formatters = { const formatters = {
system: (val: number) => `${val}%`, system: (val: number) => `${val}%`,
@ -111,11 +104,15 @@ export const demoFunc = () => {
return Math.floor(Math.random() * (max - min + 1)) + min; return Math.floor(Math.random() * (max - min + 1)) + min;
}; };
// Get chart element
const getChartElement = () => {
return chartElement;
};
// Add real-time data // Add real-time data
const addRealtimeData = () => { const addRealtimeData = () => {
if (!chartElement) return; const chart = getChartElement();
if (!chart) return;
const dataset = datasets[currentDataset];
const newTimestamp = new Date().toISOString(); const newTimestamp = new Date().toISOString();
// Generate new data points based on dataset type // Generate new data points based on dataset type
@ -133,29 +130,48 @@ export const demoFunc = () => {
]; ];
} }
// Keep only last 10 data points // Keep only last 20 data points and update without animation
const currentSeries = chartElement.series.map((series, index) => ({ const currentSeries = chart.series.map((series, index) => ({
...series, ...series,
data: [...series.data.slice(-9), ...(newData[index] || [])], data: [...series.data.slice(-19), ...(newData[index] || [])],
})); }));
chartElement.series = currentSeries; // Update without animation for smoother real-time updates
chart.updateSeries(currentSeries, false);
}; };
// Switch dataset // Switch dataset
const switchDataset = (name: string) => { const switchDataset = (name: string) => {
currentDataset = name; currentDataset = name;
if (chartElement) {
const updateChart = () => {
const chart = getChartElement();
if (chart) {
const dataset = datasets[name]; const dataset = datasets[name];
chartElement.label = dataset.label; chart.label = dataset.label;
chartElement.series = dataset.series; chart.series = dataset.series;
chartElement.yAxisFormatter = formatters[name]; chart.yAxisFormatter = formatters[name];
} }
}; };
updateChart();
};
// Start/stop real-time updates // Start/stop real-time updates
const startRealtime = () => { const startRealtime = () => {
if (!intervalId && (currentDataset === 'system' || currentDataset === 'network')) { if (!intervalId && (currentDataset === 'system' || currentDataset === 'network')) {
const chart = getChartElement();
if (chart) {
// Disable animations for real-time mode
chart.updateOptions({
chart: {
animations: {
enabled: false
}
}
}, false, false);
}
intervalId = window.setInterval(() => addRealtimeData(), 2000); intervalId = window.setInterval(() => addRealtimeData(), 2000);
} }
}; };
@ -164,27 +180,50 @@ export const demoFunc = () => {
if (intervalId) { if (intervalId) {
window.clearInterval(intervalId); window.clearInterval(intervalId);
intervalId = null; intervalId = null;
const chart = getChartElement();
if (chart) {
// Re-enable animations when stopping real-time
chart.updateOptions({
chart: {
animations: {
enabled: true,
speed: 400,
animateGradually: {
enabled: true,
delay: 150
}
}
}
}, false, true);
}
} }
}; };
// Randomize current data // Randomize current data
const randomizeData = () => { const randomizeData = () => {
if (!chartElement) return; const chart = getChartElement();
if (!chart) return;
const currentSeries = chartElement.series.map(series => ({ const currentSeries = chart.series.map(series => ({
...series, ...series,
data: series.data.map(point => ({ data: series.data.map((point: any) => ({
...point, ...point,
y: typeof point.y === 'number' y: typeof point.y === 'number'
? point.y * (0.8 + Math.random() * 0.4) // +/- 20% variation ? Math.round(point.y * (0.8 + Math.random() * 0.4)) // +/- 20% variation
: point.y, : point.y,
})), })),
})); }));
chartElement.series = currentSeries; // Update with animation for single updates
chart.updateSeries(currentSeries, true);
}; };
return html` return html`
<dees-demowrapper .runAfterRender=${async (element: HTMLElement) => {
// Store the chart element reference
chartElement = element.querySelector('dees-chart-area') as DeesChartArea;
}}>
<style> <style>
${css` ${css`
.demoBox { .demoBox {
@ -206,22 +245,6 @@ export const demoFunc = () => {
margin-bottom: 8px; margin-bottom: 8px;
} }
.control-section {
display: flex;
gap: 8px;
align-items: center;
padding: 8px;
background: rgba(255, 255, 255, 0.05);
border-radius: 6px;
}
.section-label {
color: #999;
font-size: 12px;
font-family: 'Geist Sans', sans-serif;
margin-right: 8px;
}
.chart-container { .chart-container {
flex: 1; flex: 1;
min-height: 400px; min-height: 400px;
@ -238,8 +261,7 @@ export const demoFunc = () => {
</style> </style>
<div class="demoBox"> <div class="demoBox">
<div class="controls"> <div class="controls">
<div class="control-section"> <dees-button-group label="Dataset:">
<span class="section-label">Dataset:</span>
<dees-button <dees-button
@clicked=${() => switchDataset('system')} @clicked=${() => switchDataset('system')}
type=${currentDataset === 'system' ? 'highlighted' : 'normal'} type=${currentDataset === 'system' ? 'highlighted' : 'normal'}
@ -252,19 +274,17 @@ export const demoFunc = () => {
@clicked=${() => switchDataset('sales')} @clicked=${() => switchDataset('sales')}
type=${currentDataset === 'sales' ? 'highlighted' : 'normal'} type=${currentDataset === 'sales' ? 'highlighted' : 'normal'}
>Sales Data</dees-button> >Sales Data</dees-button>
</div> </dees-button-group>
<div class="control-section"> <dees-button-group label="Real-time:">
<span class="section-label">Real-time:</span>
<dees-button @clicked=${() => startRealtime()}>Start Live</dees-button> <dees-button @clicked=${() => startRealtime()}>Start Live</dees-button>
<dees-button @clicked=${() => stopRealtime()}>Stop Live</dees-button> <dees-button @clicked=${() => stopRealtime()}>Stop Live</dees-button>
</div> </dees-button-group>
<div class="control-section"> <dees-button-group label="Actions:">
<span class="section-label">Actions:</span>
<dees-button @clicked=${() => randomizeData()}>Randomize Values</dees-button> <dees-button @clicked=${() => randomizeData()}>Randomize Values</dees-button>
<dees-button @clicked=${() => addRealtimeData()}>Add Point</dees-button> <dees-button @clicked=${() => addRealtimeData()}>Add Point</dees-button>
</div> </dees-button-group>
</div> </div>
<div class="chart-container"> <div class="chart-container">
@ -281,5 +301,6 @@ export const demoFunc = () => {
Try switching datasets and randomizing values Try switching datasets and randomizing values
</div> </div>
</div> </div>
</dees-demowrapper>
`; `;
}; };

View File

@ -6,7 +6,6 @@ import {
html, html,
property, property,
state, state,
type CSSResult,
type TemplateResult, type TemplateResult,
} from '@design.estate/dees-element'; } from '@design.estate/dees-element';
@ -35,7 +34,7 @@ export class DeesChartArea extends DeesElement {
@property({ type: Array }) @property({ type: Array })
public series: ApexAxisChartSeries = []; public series: ApexAxisChartSeries = [];
@property({ type: Function }) @property({ attribute: false })
public yAxisFormatter: (value: number) => string = (val) => `${val} Mbps`; public yAxisFormatter: (value: number) => string = (val) => `${val} Mbps`;
private resizeObserver: ResizeObserver; private resizeObserver: ResizeObserver;
@ -187,9 +186,13 @@ export class DeesChartArea extends DeesElement {
enabled: true, enabled: true,
speed: 400, speed: 400,
animateGradually: { animateGradually: {
enabled: true, enabled: false, // Disable gradual animation for cleaner updates
delay: 150 delay: 0
}, },
dynamicAnimation: {
enabled: true,
speed: 350
}
}, },
}, },
dataLabels: { dataLabels: {
@ -312,24 +315,29 @@ export class DeesChartArea extends DeesElement {
} }
} }
public async updateSeries(newSeries: ApexAxisChartSeries) { public async updateSeries(newSeries: ApexAxisChartSeries, animate: boolean = true) {
if (!this.chart) { if (!this.chart) {
return; return;
} }
await this.chart.updateSeries(newSeries, true); this.chart.updateSeries(newSeries, animate);
} }
public async appendData(seriesIndex: number, newData: any[]) { public async appendData(newData: { data: any[] }[]) {
if (!this.chart) { if (!this.chart) {
return; return;
} }
const currentSeries = [...this.series]; // Use ApexCharts' appendData method for smoother real-time updates
if (currentSeries[seriesIndex]) { await this.chart.appendData(newData);
currentSeries[seriesIndex].data = [...currentSeries[seriesIndex].data, ...newData];
await this.updateSeries(currentSeries);
} }
public async updateOptions(options: ApexCharts.ApexOptions, redrawPaths?: boolean, animate?: boolean) {
if (!this.chart) {
return;
}
return this.chart.updateOptions(options, redrawPaths, animate);
} }
public async resizeChart() { public async resizeChart() {

View File

@ -1,7 +1,10 @@
import { html } from '@design.estate/dees-element'; import { html } from '@design.estate/dees-element';
import type { DeesChartLog } from './dees-chart-log.js';
import '@design.estate/dees-wcctools';
export const demoFunc = () => { export const demoFunc = () => {
let intervalId: number; let intervalId: number;
let logElement: DeesChartLog;
const serverSources = ['Server', 'Database', 'API', 'Auth', 'Cache', 'Queue', 'WebSocket', 'Scheduler']; const serverSources = ['Server', 'Database', 'API', 'Auth', 'Cache', 'Queue', 'WebSocket', 'Scheduler'];
@ -44,7 +47,6 @@ export const demoFunc = () => {
}; };
const generateRandomLog = () => { const generateRandomLog = () => {
const logElement = (window as any).__demoLogElement;
if (!logElement) { if (!logElement) {
console.warn('Log element not ready yet'); console.warn('Log element not ready yet');
return; return;
@ -119,6 +121,10 @@ export const demoFunc = () => {
return html` return html`
<dees-demowrapper .runAfterRender=${async (element: HTMLElement) => {
// Store the log element reference
logElement = element.querySelector('dees-chart-log') as DeesChartLog;
}}>
<style> <style>
.demoBox { .demoBox {
position: relative; position: relative;
@ -153,5 +159,6 @@ export const demoFunc = () => {
.label=${'Production Server Logs'} .label=${'Production Server Logs'}
></dees-chart-log> ></dees-chart-log>
</div> </div>
</dees-demowrapper>
`; `;
}; };

View File

@ -274,11 +274,6 @@ export class DeesChartLog extends DeesElement {
this.logEntries = demoLogs; this.logEntries = demoLogs;
this.scrollToBottom(); this.scrollToBottom();
// For demo purposes, store reference globally
if ((window as any).__demoLogElement === undefined) {
(window as any).__demoLogElement = this;
}
} }
public async updateLog(entries?: ILogEntry[]) { public async updateLog(entries?: ILogEntry[]) {

View File

@ -6,6 +6,7 @@ export * from './dees-appui-mainmenu.js';
export * from './dees-appui-mainselector.js'; export * from './dees-appui-mainselector.js';
export * from './dees-badge.js'; export * from './dees-badge.js';
export * from './dees-button-exit.js'; export * from './dees-button-exit.js';
export * from './dees-button-group.js';
export * from './dees-button.js'; export * from './dees-button.js';
export * from './dees-chart-area.js'; export * from './dees-chart-area.js';
export * from './dees-chart-log.js'; export * from './dees-chart-log.js';