@zentto/report-designer 1.5.6 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/data-panel/data-source-tree.d.ts +46 -0
- package/dist/data-panel/data-source-tree.d.ts.map +1 -0
- package/dist/data-panel/data-source-tree.js +498 -0
- package/dist/data-panel/data-source-tree.js.map +1 -0
- package/dist/data-panel/db-connector.d.ts +77 -0
- package/dist/data-panel/db-connector.d.ts.map +1 -0
- package/dist/data-panel/db-connector.js +602 -0
- package/dist/data-panel/db-connector.js.map +1 -0
- package/dist/data-panel/er-diagram.d.ts +29 -0
- package/dist/data-panel/er-diagram.d.ts.map +1 -0
- package/dist/data-panel/er-diagram.js +425 -0
- package/dist/data-panel/er-diagram.js.map +1 -0
- package/dist/data-panel/relation-editor.d.ts +29 -0
- package/dist/data-panel/relation-editor.d.ts.map +1 -0
- package/dist/data-panel/relation-editor.js +424 -0
- package/dist/data-panel/relation-editor.js.map +1 -0
- package/dist/zentto-report-designer.d.ts +28 -0
- package/dist/zentto-report-designer.d.ts.map +1 -1
- package/dist/zentto-report-designer.js +242 -34
- package/dist/zentto-report-designer.js.map +1 -1
- package/package.json +17 -1
|
@@ -7,7 +7,12 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
7
7
|
// @zentto/report-designer — WYSIWYG report designer web component
|
|
8
8
|
import { LitElement, html, css, nothing } from 'lit';
|
|
9
9
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
10
|
-
import { createBlankLayout, toPx, PAGE_SIZES } from '@zentto/report-core';
|
|
10
|
+
import { createBlankLayout, toPx, PAGE_SIZES, mergeStyles, styleToCss } from '@zentto/report-core';
|
|
11
|
+
// Data panel components (tree, relation editor, ER diagram, DB connector)
|
|
12
|
+
import './data-panel/data-source-tree.js';
|
|
13
|
+
import './data-panel/relation-editor.js';
|
|
14
|
+
import './data-panel/er-diagram.js';
|
|
15
|
+
import './data-panel/db-connector.js';
|
|
11
16
|
const BAND_LABELS = {
|
|
12
17
|
reportHeader: 'Report Header',
|
|
13
18
|
pageHeader: 'Page Header',
|
|
@@ -83,6 +88,10 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
83
88
|
this.sampleData = null;
|
|
84
89
|
/** Available data sources for the field picker */
|
|
85
90
|
this.dataSources = [];
|
|
91
|
+
/** Data panel provider for lazy-loading from DB (Studio integration) */
|
|
92
|
+
this.dataProvider = null;
|
|
93
|
+
/** DB connector provider — enables native database connections in the web component */
|
|
94
|
+
this.dbProvider = null;
|
|
86
95
|
/** Grid snap size in layout units (mm) */
|
|
87
96
|
this.gridSnap = 1;
|
|
88
97
|
/** Show live preview panel */
|
|
@@ -109,6 +118,10 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
109
118
|
this._resize = null;
|
|
110
119
|
this._bandResize = null;
|
|
111
120
|
this._activePanel = 'toolbox';
|
|
121
|
+
this._relationEditorOpen = false;
|
|
122
|
+
this._editingRelation = null;
|
|
123
|
+
this._erOverlayOpen = false;
|
|
124
|
+
this._dbConnectorOpen = false;
|
|
112
125
|
this._undoStack = [];
|
|
113
126
|
this._redoStack = [];
|
|
114
127
|
this._editingReportName = false;
|
|
@@ -2961,28 +2974,159 @@ REGLAS:
|
|
|
2961
2974
|
`)}
|
|
2962
2975
|
`;
|
|
2963
2976
|
}
|
|
2964
|
-
// ─── Data Panel
|
|
2977
|
+
// ─── Data Panel (Tree + Actions) ─────────────────────────────
|
|
2965
2978
|
_renderDataPanel() {
|
|
2966
|
-
if (this._layout.dataSources.length === 0) {
|
|
2967
|
-
return html `<div style="color:var(--zrd-text-muted);padding:12px;">No data sources configured.</div>`;
|
|
2968
|
-
}
|
|
2969
2979
|
return html `
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2980
|
+
<div style="padding:6px 8px;display:flex;gap:4px;border-bottom:1px solid var(--zrd-border,#e0e0e0);flex-shrink:0;">
|
|
2981
|
+
${this.dbProvider ? html `
|
|
2982
|
+
<button style="flex:1;padding:5px 8px;border:1px solid var(--zrd-border,#ddd);border-radius:4px;cursor:pointer;font-size:10px;font-weight:600;background:var(--zrd-input-bg,#fff);color:var(--zrd-primary,#1976d2);"
|
|
2983
|
+
@click=${() => { this._dbConnectorOpen = true; }}>
|
|
2984
|
+
\u{1F5C4} Connect DB
|
|
2985
|
+
</button>
|
|
2986
|
+
` : nothing}
|
|
2987
|
+
${this._layout.dataSources.length > 0 ? html `
|
|
2988
|
+
<button style="flex:1;padding:5px 8px;border:1px solid var(--zrd-border,#ddd);border-radius:4px;cursor:pointer;font-size:10px;font-weight:600;background:var(--zrd-input-bg,#fff);color:var(--zrd-text,#555);"
|
|
2989
|
+
@click=${() => { this._erOverlayOpen = true; }}>
|
|
2990
|
+
\u{1F4CA} ER Diagram
|
|
2991
|
+
</button>
|
|
2992
|
+
` : nothing}
|
|
2993
|
+
</div>
|
|
2994
|
+
<zrd-data-source-tree
|
|
2995
|
+
.dataSources=${this._layout.dataSources}
|
|
2996
|
+
.relations=${this._layout.relations || []}
|
|
2997
|
+
.dataProvider=${this.dataProvider}
|
|
2998
|
+
@tree-action=${(e) => this._onTreeAction(e.detail)}
|
|
2999
|
+
@field-double-click=${(e) => this._onFieldDoubleClick(e.detail)}
|
|
3000
|
+
></zrd-data-source-tree>
|
|
3001
|
+
${this._renderOverlays()}
|
|
3002
|
+
`;
|
|
3003
|
+
}
|
|
3004
|
+
// ─── Overlays (ER Diagram + DB Connector + Relation Editor) ──
|
|
3005
|
+
_renderOverlays() {
|
|
3006
|
+
return html `
|
|
3007
|
+
${this._erOverlayOpen ? this._renderErOverlay() : nothing}
|
|
3008
|
+
<zrd-relation-editor
|
|
3009
|
+
.dataSources=${this._layout.dataSources}
|
|
3010
|
+
.relation=${this._editingRelation}
|
|
3011
|
+
.open=${this._relationEditorOpen}
|
|
3012
|
+
@relation-save=${(e) => this._onRelationSave(e.detail.relation)}
|
|
3013
|
+
@relation-cancel=${() => { this._relationEditorOpen = false; this._editingRelation = null; }}
|
|
3014
|
+
></zrd-relation-editor>
|
|
3015
|
+
<zrd-db-connector
|
|
3016
|
+
.open=${this._dbConnectorOpen}
|
|
3017
|
+
.provider=${this.dbProvider}
|
|
3018
|
+
@datasources-ready=${(e) => this._onDatasourcesReady(e.detail)}
|
|
3019
|
+
@connector-close=${() => { this._dbConnectorOpen = false; }}
|
|
3020
|
+
></zrd-db-connector>
|
|
3021
|
+
`;
|
|
3022
|
+
}
|
|
3023
|
+
_renderErOverlay() {
|
|
3024
|
+
return html `
|
|
3025
|
+
<div style="position:fixed;inset:0;background:rgba(0,0,0,0.5);z-index:10000;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(2px);"
|
|
3026
|
+
@click=${() => { this._erOverlayOpen = false; }}>
|
|
3027
|
+
<div style="background:var(--zrd-panel-bg,#fff);border-radius:12px;box-shadow:0 20px 60px rgba(0,0,0,0.3);width:85vw;height:80vh;display:flex;flex-direction:column;overflow:hidden;"
|
|
3028
|
+
@click=${(e) => e.stopPropagation()}>
|
|
3029
|
+
<div style="padding:16px 20px;border-bottom:1px solid var(--zrd-border,#eee);display:flex;justify-content:space-between;align-items:center;">
|
|
3030
|
+
<span style="font-size:16px;font-weight:600;">ER Diagram</span>
|
|
3031
|
+
<button style="background:none;border:none;font-size:20px;cursor:pointer;color:var(--zrd-text-muted,#999);width:32px;height:32px;border-radius:6px;display:flex;align-items:center;justify-content:center;"
|
|
3032
|
+
@click=${() => { this._erOverlayOpen = false; }}>\u2715</button>
|
|
2981
3033
|
</div>
|
|
2982
|
-
|
|
2983
|
-
|
|
3034
|
+
<div style="flex:1;overflow:auto;">
|
|
3035
|
+
<zrd-er-diagram
|
|
3036
|
+
.dataSources=${this._layout.dataSources}
|
|
3037
|
+
.relations=${this._layout.relations || []}
|
|
3038
|
+
@relation-edit=${(e) => {
|
|
3039
|
+
this._editingRelation = e.detail.relation;
|
|
3040
|
+
this._relationEditorOpen = true;
|
|
3041
|
+
}}
|
|
3042
|
+
@table-focus=${() => {
|
|
3043
|
+
this._erOverlayOpen = false;
|
|
3044
|
+
this._activePanel = 'data';
|
|
3045
|
+
}}
|
|
3046
|
+
></zrd-er-diagram>
|
|
3047
|
+
</div>
|
|
3048
|
+
</div>
|
|
3049
|
+
</div>
|
|
2984
3050
|
`;
|
|
2985
3051
|
}
|
|
3052
|
+
// ─── DB Connector Event Handler ────────────────────────────────
|
|
3053
|
+
_onDatasourcesReady(detail) {
|
|
3054
|
+
// Merge new data sources with existing ones
|
|
3055
|
+
const existingIds = new Set(this._layout.dataSources.map(ds => ds.id));
|
|
3056
|
+
const newSources = detail.dataSources.filter(ds => !existingIds.has(ds.id));
|
|
3057
|
+
if (newSources.length > 0) {
|
|
3058
|
+
this._layout = {
|
|
3059
|
+
...this._layout,
|
|
3060
|
+
dataSources: [...this._layout.dataSources, ...newSources],
|
|
3061
|
+
};
|
|
3062
|
+
// Emit sample data for the host app to pick up
|
|
3063
|
+
this.dispatchEvent(new CustomEvent('sample-data-update', {
|
|
3064
|
+
detail: { sampleData: detail.sampleData },
|
|
3065
|
+
bubbles: true, composed: true,
|
|
3066
|
+
}));
|
|
3067
|
+
this._commitChange();
|
|
3068
|
+
}
|
|
3069
|
+
this._dbConnectorOpen = false;
|
|
3070
|
+
}
|
|
3071
|
+
// ─── Tree/Relation Event Handlers ──────────────────────────────
|
|
3072
|
+
_onTreeAction(detail) {
|
|
3073
|
+
switch (detail.action) {
|
|
3074
|
+
case 'create-relation':
|
|
3075
|
+
this._editingRelation = null;
|
|
3076
|
+
this._relationEditorOpen = true;
|
|
3077
|
+
break;
|
|
3078
|
+
case 'show-in-er':
|
|
3079
|
+
this._erOverlayOpen = true;
|
|
3080
|
+
break;
|
|
3081
|
+
case 'add-all-fields': {
|
|
3082
|
+
const dsId = detail.node.dataSourceId;
|
|
3083
|
+
if (!dsId)
|
|
3084
|
+
return;
|
|
3085
|
+
const ds = this._layout.dataSources.find(d => d.id === dsId);
|
|
3086
|
+
if (!ds?.fields)
|
|
3087
|
+
return;
|
|
3088
|
+
// Find or create detail band
|
|
3089
|
+
let detailBand = this._layout.bands.find(b => b.type === 'detail');
|
|
3090
|
+
if (!detailBand)
|
|
3091
|
+
return;
|
|
3092
|
+
let xOffset = 2;
|
|
3093
|
+
for (const f of ds.fields) {
|
|
3094
|
+
this._addElementToBand(detailBand.id, 'field', {
|
|
3095
|
+
dataSource: dsId,
|
|
3096
|
+
field: f.name,
|
|
3097
|
+
x: xOffset,
|
|
3098
|
+
y: 2,
|
|
3099
|
+
});
|
|
3100
|
+
xOffset += 35;
|
|
3101
|
+
}
|
|
3102
|
+
break;
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
}
|
|
3106
|
+
_onFieldDoubleClick(detail) {
|
|
3107
|
+
// Add field to detail band at next available position
|
|
3108
|
+
const detailBand = this._layout.bands.find(b => b.type === 'detail');
|
|
3109
|
+
if (!detailBand)
|
|
3110
|
+
return;
|
|
3111
|
+
this._addElementToBand(detailBand.id, 'field', {
|
|
3112
|
+
dataSource: detail.dataSourceId,
|
|
3113
|
+
field: detail.field.name,
|
|
3114
|
+
});
|
|
3115
|
+
}
|
|
3116
|
+
_onRelationSave(relation) {
|
|
3117
|
+
const relations = [...(this._layout.relations || [])];
|
|
3118
|
+
const idx = relations.findIndex(r => r.id === relation.id);
|
|
3119
|
+
if (idx >= 0) {
|
|
3120
|
+
relations[idx] = relation;
|
|
3121
|
+
}
|
|
3122
|
+
else {
|
|
3123
|
+
relations.push(relation);
|
|
3124
|
+
}
|
|
3125
|
+
this._layout = { ...this._layout, relations };
|
|
3126
|
+
this._relationEditorOpen = false;
|
|
3127
|
+
this._editingRelation = null;
|
|
3128
|
+
this._commitChange();
|
|
3129
|
+
}
|
|
2986
3130
|
// ─── Band Rendering ───────────────────────────────────────────
|
|
2987
3131
|
_renderBand(band) {
|
|
2988
3132
|
const bandHeightPx = this._toPx(band.height);
|
|
@@ -3004,7 +3148,7 @@ REGLAS:
|
|
|
3004
3148
|
}}>
|
|
3005
3149
|
<div class="band-header-left">
|
|
3006
3150
|
<span class="band-type-icon">${BAND_ICONS[band.type] || ''}</span>
|
|
3007
|
-
<span>${
|
|
3151
|
+
<span>${this._getBandLabel(band)}</span>
|
|
3008
3152
|
</div>
|
|
3009
3153
|
<span style="font-weight:normal">${band.height}${this._unit}</span>
|
|
3010
3154
|
</div>
|
|
@@ -3012,12 +3156,13 @@ REGLAS:
|
|
|
3012
3156
|
style="height:${bandHeightPx}px;background-color:${bgColor};background-size:${gridSizePx}px ${gridSizePx}px;"
|
|
3013
3157
|
@drop=${(e) => this._onBandDrop(e, band.id)}
|
|
3014
3158
|
@dragover=${this._onBandDragOver}
|
|
3015
|
-
@
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3159
|
+
@mousedown=${(e) => {
|
|
3160
|
+
// Only deselect if clicking directly on the band body (not on a child element)
|
|
3161
|
+
if (e.target === e.currentTarget) {
|
|
3162
|
+
this._selectedBandId = band.id;
|
|
3163
|
+
this._selectedElementIds = new Set();
|
|
3164
|
+
this.requestUpdate();
|
|
3165
|
+
}
|
|
3021
3166
|
}}>
|
|
3022
3167
|
${band.elements.length === 0 ? html `<div class="band-empty-hint">Drag elements from Toolbox or drop fields from Data panel</div>` : nothing}
|
|
3023
3168
|
${band.elements.map(el => this._renderDesignElement(el, band.id))}
|
|
@@ -3032,6 +3177,56 @@ REGLAS:
|
|
|
3032
3177
|
</div>
|
|
3033
3178
|
`;
|
|
3034
3179
|
}
|
|
3180
|
+
// ─── Sub-Band Helpers ──────────────────────────────────────────
|
|
3181
|
+
/** Generate dynamic band label with sub-band suffix (A, B, C…) */
|
|
3182
|
+
_getBandLabel(band) {
|
|
3183
|
+
const base = BAND_LABELS[band.type] || band.type;
|
|
3184
|
+
// Count how many bands of same type exist
|
|
3185
|
+
const siblingsCount = this._layout.bands.filter(b => b.type === band.type && b.visible !== false).length;
|
|
3186
|
+
if (siblingsCount <= 1 && (band.subBandIndex ?? 0) === 0)
|
|
3187
|
+
return base;
|
|
3188
|
+
const suffix = String.fromCharCode(65 + (band.subBandIndex ?? 0)); // A, B, C…
|
|
3189
|
+
return `${base} ${suffix}`;
|
|
3190
|
+
}
|
|
3191
|
+
/** Add a sub-band of the same type below the selected band */
|
|
3192
|
+
_addSubBand(bandId) {
|
|
3193
|
+
const band = this._layout.bands.find(b => b.id === bandId);
|
|
3194
|
+
if (!band)
|
|
3195
|
+
return;
|
|
3196
|
+
const siblings = this._layout.bands.filter(b => b.type === band.type);
|
|
3197
|
+
const maxIdx = Math.max(...siblings.map(b => b.subBandIndex ?? 0));
|
|
3198
|
+
const newBand = {
|
|
3199
|
+
id: `band_${Date.now()}`,
|
|
3200
|
+
type: band.type,
|
|
3201
|
+
height: band.height,
|
|
3202
|
+
elements: [],
|
|
3203
|
+
subBandIndex: maxIdx + 1,
|
|
3204
|
+
dataSource: band.dataSource,
|
|
3205
|
+
groupField: band.groupField,
|
|
3206
|
+
};
|
|
3207
|
+
// Insert after the current band
|
|
3208
|
+
const bands = [...this._layout.bands];
|
|
3209
|
+
const idx = bands.findIndex(b => b.id === bandId);
|
|
3210
|
+
bands.splice(idx + 1, 0, newBand);
|
|
3211
|
+
this._layout = { ...this._layout, bands };
|
|
3212
|
+
this._commitChange();
|
|
3213
|
+
}
|
|
3214
|
+
/** Remove a sub-band (only if there are multiple of same type) */
|
|
3215
|
+
_removeSubBand(bandId) {
|
|
3216
|
+
const band = this._layout.bands.find(b => b.id === bandId);
|
|
3217
|
+
if (!band)
|
|
3218
|
+
return;
|
|
3219
|
+
const siblings = this._layout.bands.filter(b => b.type === band.type);
|
|
3220
|
+
if (siblings.length <= 1)
|
|
3221
|
+
return; // Don't remove the last one
|
|
3222
|
+
this._layout = {
|
|
3223
|
+
...this._layout,
|
|
3224
|
+
bands: this._layout.bands.filter(b => b.id !== bandId),
|
|
3225
|
+
};
|
|
3226
|
+
if (this._selectedBandId === bandId)
|
|
3227
|
+
this._selectedBandId = null;
|
|
3228
|
+
this._commitChange();
|
|
3229
|
+
}
|
|
3035
3230
|
// ─── Element Rendering (Canvas) ───────────────────────────────
|
|
3036
3231
|
_renderDesignElement(el, bandId) {
|
|
3037
3232
|
const isSingle = this._selectedElementIds.size === 1 && this._selectedElementIds.has(el.id);
|
|
@@ -3073,15 +3268,10 @@ REGLAS:
|
|
|
3073
3268
|
break;
|
|
3074
3269
|
default: content = `[${el.type}]`;
|
|
3075
3270
|
}
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
font-family:${style.fontFamily || 'inherit'};
|
|
3081
|
-
color:${style.color || 'inherit'};
|
|
3082
|
-
background:${style.backgroundColor || 'transparent'};
|
|
3083
|
-
text-align:${style.textAlign || 'left'};
|
|
3084
|
-
`;
|
|
3271
|
+
// Merge default + element style (same as viewer/renderer for pixel-perfect)
|
|
3272
|
+
const merged = mergeStyles(this._layout.defaultStyle, style);
|
|
3273
|
+
const cssStyle = styleToCss(merged);
|
|
3274
|
+
const elStyle = `left:${xPx}px;top:${yPx}px;width:${wPx}px;height:${hPx}px;${cssStyle}`;
|
|
3085
3275
|
if (el.type === 'line') {
|
|
3086
3276
|
return html `
|
|
3087
3277
|
<div class="element ${isSingle ? 'selected' : ''} ${isMulti ? 'multi-selected' : ''}"
|
|
@@ -3778,6 +3968,12 @@ __decorate([
|
|
|
3778
3968
|
__decorate([
|
|
3779
3969
|
property({ type: Array })
|
|
3780
3970
|
], ZenttoReportDesigner.prototype, "dataSources", void 0);
|
|
3971
|
+
__decorate([
|
|
3972
|
+
property({ attribute: false })
|
|
3973
|
+
], ZenttoReportDesigner.prototype, "dataProvider", void 0);
|
|
3974
|
+
__decorate([
|
|
3975
|
+
property({ attribute: false })
|
|
3976
|
+
], ZenttoReportDesigner.prototype, "dbProvider", void 0);
|
|
3781
3977
|
__decorate([
|
|
3782
3978
|
property({ type: Number, attribute: 'grid-snap' })
|
|
3783
3979
|
], ZenttoReportDesigner.prototype, "gridSnap", void 0);
|
|
@@ -3826,6 +4022,18 @@ __decorate([
|
|
|
3826
4022
|
__decorate([
|
|
3827
4023
|
state()
|
|
3828
4024
|
], ZenttoReportDesigner.prototype, "_activePanel", void 0);
|
|
4025
|
+
__decorate([
|
|
4026
|
+
state()
|
|
4027
|
+
], ZenttoReportDesigner.prototype, "_relationEditorOpen", void 0);
|
|
4028
|
+
__decorate([
|
|
4029
|
+
state()
|
|
4030
|
+
], ZenttoReportDesigner.prototype, "_editingRelation", void 0);
|
|
4031
|
+
__decorate([
|
|
4032
|
+
state()
|
|
4033
|
+
], ZenttoReportDesigner.prototype, "_erOverlayOpen", void 0);
|
|
4034
|
+
__decorate([
|
|
4035
|
+
state()
|
|
4036
|
+
], ZenttoReportDesigner.prototype, "_dbConnectorOpen", void 0);
|
|
3829
4037
|
__decorate([
|
|
3830
4038
|
state()
|
|
3831
4039
|
], ZenttoReportDesigner.prototype, "_undoStack", void 0);
|