@zentto/report-designer 1.5.6 → 1.5.7
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/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 +19 -0
- package/dist/zentto-report-designer.d.ts.map +1 -1
- package/dist/zentto-report-designer.js +173 -35
- package/dist/zentto-report-designer.js.map +1 -1
- package/package.json +13 -1
|
@@ -7,7 +7,11 @@ 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)
|
|
12
|
+
import './data-panel/data-source-tree.js';
|
|
13
|
+
import './data-panel/relation-editor.js';
|
|
14
|
+
import './data-panel/er-diagram.js';
|
|
11
15
|
const BAND_LABELS = {
|
|
12
16
|
reportHeader: 'Report Header',
|
|
13
17
|
pageHeader: 'Page Header',
|
|
@@ -83,6 +87,8 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
83
87
|
this.sampleData = null;
|
|
84
88
|
/** Available data sources for the field picker */
|
|
85
89
|
this.dataSources = [];
|
|
90
|
+
/** Data panel provider for lazy-loading from DB (Studio integration) */
|
|
91
|
+
this.dataProvider = null;
|
|
86
92
|
/** Grid snap size in layout units (mm) */
|
|
87
93
|
this.gridSnap = 1;
|
|
88
94
|
/** Show live preview panel */
|
|
@@ -109,6 +115,8 @@ let ZenttoReportDesigner = class ZenttoReportDesigner extends LitElement {
|
|
|
109
115
|
this._resize = null;
|
|
110
116
|
this._bandResize = null;
|
|
111
117
|
this._activePanel = 'toolbox';
|
|
118
|
+
this._relationEditorOpen = false;
|
|
119
|
+
this._editingRelation = null;
|
|
112
120
|
this._undoStack = [];
|
|
113
121
|
this._redoStack = [];
|
|
114
122
|
this._editingReportName = false;
|
|
@@ -2320,10 +2328,13 @@ REGLAS:
|
|
|
2320
2328
|
@click=${() => this._activePanel = 'toolbox'}>Toolbox</div>
|
|
2321
2329
|
<div class="panel-tab ${this._activePanel === 'data' ? 'active' : ''}"
|
|
2322
2330
|
@click=${() => this._activePanel = 'data'}>Data</div>
|
|
2331
|
+
<div class="panel-tab ${this._activePanel === 'er' ? 'active' : ''}"
|
|
2332
|
+
@click=${() => this._activePanel = 'er'}>ER</div>
|
|
2323
2333
|
</div>
|
|
2324
2334
|
<div class="panel-content">
|
|
2325
2335
|
${this._activePanel === 'toolbox' ? this._renderToolbox() : null}
|
|
2326
2336
|
${this._activePanel === 'data' ? this._renderDataPanel() : null}
|
|
2337
|
+
${this._activePanel === 'er' ? this._renderErPanel() : null}
|
|
2327
2338
|
</div>
|
|
2328
2339
|
</div>
|
|
2329
2340
|
|
|
@@ -2961,28 +2972,100 @@ REGLAS:
|
|
|
2961
2972
|
`)}
|
|
2962
2973
|
`;
|
|
2963
2974
|
}
|
|
2964
|
-
// ─── Data Panel
|
|
2975
|
+
// ─── Data Panel (Tree) ────────────────────────────────────────
|
|
2965
2976
|
_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
2977
|
return html `
|
|
2970
|
-
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
}
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2978
|
+
<zrd-data-source-tree
|
|
2979
|
+
.dataSources=${this._layout.dataSources}
|
|
2980
|
+
.relations=${this._layout.relations || []}
|
|
2981
|
+
.dataProvider=${this.dataProvider}
|
|
2982
|
+
@tree-action=${(e) => this._onTreeAction(e.detail)}
|
|
2983
|
+
@field-double-click=${(e) => this._onFieldDoubleClick(e.detail)}
|
|
2984
|
+
></zrd-data-source-tree>
|
|
2985
|
+
<zrd-relation-editor
|
|
2986
|
+
.dataSources=${this._layout.dataSources}
|
|
2987
|
+
.relation=${this._editingRelation}
|
|
2988
|
+
.open=${this._relationEditorOpen}
|
|
2989
|
+
@relation-save=${(e) => this._onRelationSave(e.detail.relation)}
|
|
2990
|
+
@relation-cancel=${() => { this._relationEditorOpen = false; this._editingRelation = null; }}
|
|
2991
|
+
></zrd-relation-editor>
|
|
2992
|
+
`;
|
|
2993
|
+
}
|
|
2994
|
+
// ─── ER Diagram Panel ──────────────────────────────────────────
|
|
2995
|
+
_renderErPanel() {
|
|
2996
|
+
return html `
|
|
2997
|
+
<zrd-er-diagram
|
|
2998
|
+
.dataSources=${this._layout.dataSources}
|
|
2999
|
+
.relations=${this._layout.relations || []}
|
|
3000
|
+
@relation-edit=${(e) => {
|
|
3001
|
+
this._editingRelation = e.detail.relation;
|
|
3002
|
+
this._relationEditorOpen = true;
|
|
3003
|
+
}}
|
|
3004
|
+
@table-focus=${(e) => {
|
|
3005
|
+
this._activePanel = 'data';
|
|
3006
|
+
}}
|
|
3007
|
+
></zrd-er-diagram>
|
|
2984
3008
|
`;
|
|
2985
3009
|
}
|
|
3010
|
+
// ─── Tree/Relation Event Handlers ──────────────────────────────
|
|
3011
|
+
_onTreeAction(detail) {
|
|
3012
|
+
switch (detail.action) {
|
|
3013
|
+
case 'create-relation':
|
|
3014
|
+
this._editingRelation = null;
|
|
3015
|
+
this._relationEditorOpen = true;
|
|
3016
|
+
break;
|
|
3017
|
+
case 'show-in-er':
|
|
3018
|
+
this._activePanel = 'er';
|
|
3019
|
+
break;
|
|
3020
|
+
case 'add-all-fields': {
|
|
3021
|
+
const dsId = detail.node.dataSourceId;
|
|
3022
|
+
if (!dsId)
|
|
3023
|
+
return;
|
|
3024
|
+
const ds = this._layout.dataSources.find(d => d.id === dsId);
|
|
3025
|
+
if (!ds?.fields)
|
|
3026
|
+
return;
|
|
3027
|
+
// Find or create detail band
|
|
3028
|
+
let detailBand = this._layout.bands.find(b => b.type === 'detail');
|
|
3029
|
+
if (!detailBand)
|
|
3030
|
+
return;
|
|
3031
|
+
let xOffset = 2;
|
|
3032
|
+
for (const f of ds.fields) {
|
|
3033
|
+
this._addElementToBand(detailBand.id, 'field', {
|
|
3034
|
+
dataSource: dsId,
|
|
3035
|
+
field: f.name,
|
|
3036
|
+
x: xOffset,
|
|
3037
|
+
y: 2,
|
|
3038
|
+
});
|
|
3039
|
+
xOffset += 35;
|
|
3040
|
+
}
|
|
3041
|
+
break;
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
_onFieldDoubleClick(detail) {
|
|
3046
|
+
// Add field to detail band at next available position
|
|
3047
|
+
const detailBand = this._layout.bands.find(b => b.type === 'detail');
|
|
3048
|
+
if (!detailBand)
|
|
3049
|
+
return;
|
|
3050
|
+
this._addElementToBand(detailBand.id, 'field', {
|
|
3051
|
+
dataSource: detail.dataSourceId,
|
|
3052
|
+
field: detail.field.name,
|
|
3053
|
+
});
|
|
3054
|
+
}
|
|
3055
|
+
_onRelationSave(relation) {
|
|
3056
|
+
const relations = [...(this._layout.relations || [])];
|
|
3057
|
+
const idx = relations.findIndex(r => r.id === relation.id);
|
|
3058
|
+
if (idx >= 0) {
|
|
3059
|
+
relations[idx] = relation;
|
|
3060
|
+
}
|
|
3061
|
+
else {
|
|
3062
|
+
relations.push(relation);
|
|
3063
|
+
}
|
|
3064
|
+
this._layout = { ...this._layout, relations };
|
|
3065
|
+
this._relationEditorOpen = false;
|
|
3066
|
+
this._editingRelation = null;
|
|
3067
|
+
this._commitChange();
|
|
3068
|
+
}
|
|
2986
3069
|
// ─── Band Rendering ───────────────────────────────────────────
|
|
2987
3070
|
_renderBand(band) {
|
|
2988
3071
|
const bandHeightPx = this._toPx(band.height);
|
|
@@ -3004,7 +3087,7 @@ REGLAS:
|
|
|
3004
3087
|
}}>
|
|
3005
3088
|
<div class="band-header-left">
|
|
3006
3089
|
<span class="band-type-icon">${BAND_ICONS[band.type] || ''}</span>
|
|
3007
|
-
<span>${
|
|
3090
|
+
<span>${this._getBandLabel(band)}</span>
|
|
3008
3091
|
</div>
|
|
3009
3092
|
<span style="font-weight:normal">${band.height}${this._unit}</span>
|
|
3010
3093
|
</div>
|
|
@@ -3012,12 +3095,13 @@ REGLAS:
|
|
|
3012
3095
|
style="height:${bandHeightPx}px;background-color:${bgColor};background-size:${gridSizePx}px ${gridSizePx}px;"
|
|
3013
3096
|
@drop=${(e) => this._onBandDrop(e, band.id)}
|
|
3014
3097
|
@dragover=${this._onBandDragOver}
|
|
3015
|
-
@
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3098
|
+
@mousedown=${(e) => {
|
|
3099
|
+
// Only deselect if clicking directly on the band body (not on a child element)
|
|
3100
|
+
if (e.target === e.currentTarget) {
|
|
3101
|
+
this._selectedBandId = band.id;
|
|
3102
|
+
this._selectedElementIds = new Set();
|
|
3103
|
+
this.requestUpdate();
|
|
3104
|
+
}
|
|
3021
3105
|
}}>
|
|
3022
3106
|
${band.elements.length === 0 ? html `<div class="band-empty-hint">Drag elements from Toolbox or drop fields from Data panel</div>` : nothing}
|
|
3023
3107
|
${band.elements.map(el => this._renderDesignElement(el, band.id))}
|
|
@@ -3032,6 +3116,56 @@ REGLAS:
|
|
|
3032
3116
|
</div>
|
|
3033
3117
|
`;
|
|
3034
3118
|
}
|
|
3119
|
+
// ─── Sub-Band Helpers ──────────────────────────────────────────
|
|
3120
|
+
/** Generate dynamic band label with sub-band suffix (A, B, C…) */
|
|
3121
|
+
_getBandLabel(band) {
|
|
3122
|
+
const base = BAND_LABELS[band.type] || band.type;
|
|
3123
|
+
// Count how many bands of same type exist
|
|
3124
|
+
const siblingsCount = this._layout.bands.filter(b => b.type === band.type && b.visible !== false).length;
|
|
3125
|
+
if (siblingsCount <= 1 && (band.subBandIndex ?? 0) === 0)
|
|
3126
|
+
return base;
|
|
3127
|
+
const suffix = String.fromCharCode(65 + (band.subBandIndex ?? 0)); // A, B, C…
|
|
3128
|
+
return `${base} ${suffix}`;
|
|
3129
|
+
}
|
|
3130
|
+
/** Add a sub-band of the same type below the selected band */
|
|
3131
|
+
_addSubBand(bandId) {
|
|
3132
|
+
const band = this._layout.bands.find(b => b.id === bandId);
|
|
3133
|
+
if (!band)
|
|
3134
|
+
return;
|
|
3135
|
+
const siblings = this._layout.bands.filter(b => b.type === band.type);
|
|
3136
|
+
const maxIdx = Math.max(...siblings.map(b => b.subBandIndex ?? 0));
|
|
3137
|
+
const newBand = {
|
|
3138
|
+
id: `band_${Date.now()}`,
|
|
3139
|
+
type: band.type,
|
|
3140
|
+
height: band.height,
|
|
3141
|
+
elements: [],
|
|
3142
|
+
subBandIndex: maxIdx + 1,
|
|
3143
|
+
dataSource: band.dataSource,
|
|
3144
|
+
groupField: band.groupField,
|
|
3145
|
+
};
|
|
3146
|
+
// Insert after the current band
|
|
3147
|
+
const bands = [...this._layout.bands];
|
|
3148
|
+
const idx = bands.findIndex(b => b.id === bandId);
|
|
3149
|
+
bands.splice(idx + 1, 0, newBand);
|
|
3150
|
+
this._layout = { ...this._layout, bands };
|
|
3151
|
+
this._commitChange();
|
|
3152
|
+
}
|
|
3153
|
+
/** Remove a sub-band (only if there are multiple of same type) */
|
|
3154
|
+
_removeSubBand(bandId) {
|
|
3155
|
+
const band = this._layout.bands.find(b => b.id === bandId);
|
|
3156
|
+
if (!band)
|
|
3157
|
+
return;
|
|
3158
|
+
const siblings = this._layout.bands.filter(b => b.type === band.type);
|
|
3159
|
+
if (siblings.length <= 1)
|
|
3160
|
+
return; // Don't remove the last one
|
|
3161
|
+
this._layout = {
|
|
3162
|
+
...this._layout,
|
|
3163
|
+
bands: this._layout.bands.filter(b => b.id !== bandId),
|
|
3164
|
+
};
|
|
3165
|
+
if (this._selectedBandId === bandId)
|
|
3166
|
+
this._selectedBandId = null;
|
|
3167
|
+
this._commitChange();
|
|
3168
|
+
}
|
|
3035
3169
|
// ─── Element Rendering (Canvas) ───────────────────────────────
|
|
3036
3170
|
_renderDesignElement(el, bandId) {
|
|
3037
3171
|
const isSingle = this._selectedElementIds.size === 1 && this._selectedElementIds.has(el.id);
|
|
@@ -3073,15 +3207,10 @@ REGLAS:
|
|
|
3073
3207
|
break;
|
|
3074
3208
|
default: content = `[${el.type}]`;
|
|
3075
3209
|
}
|
|
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
|
-
`;
|
|
3210
|
+
// Merge default + element style (same as viewer/renderer for pixel-perfect)
|
|
3211
|
+
const merged = mergeStyles(this._layout.defaultStyle, style);
|
|
3212
|
+
const cssStyle = styleToCss(merged);
|
|
3213
|
+
const elStyle = `left:${xPx}px;top:${yPx}px;width:${wPx}px;height:${hPx}px;${cssStyle}`;
|
|
3085
3214
|
if (el.type === 'line') {
|
|
3086
3215
|
return html `
|
|
3087
3216
|
<div class="element ${isSingle ? 'selected' : ''} ${isMulti ? 'multi-selected' : ''}"
|
|
@@ -3778,6 +3907,9 @@ __decorate([
|
|
|
3778
3907
|
__decorate([
|
|
3779
3908
|
property({ type: Array })
|
|
3780
3909
|
], ZenttoReportDesigner.prototype, "dataSources", void 0);
|
|
3910
|
+
__decorate([
|
|
3911
|
+
property({ attribute: false })
|
|
3912
|
+
], ZenttoReportDesigner.prototype, "dataProvider", void 0);
|
|
3781
3913
|
__decorate([
|
|
3782
3914
|
property({ type: Number, attribute: 'grid-snap' })
|
|
3783
3915
|
], ZenttoReportDesigner.prototype, "gridSnap", void 0);
|
|
@@ -3826,6 +3958,12 @@ __decorate([
|
|
|
3826
3958
|
__decorate([
|
|
3827
3959
|
state()
|
|
3828
3960
|
], ZenttoReportDesigner.prototype, "_activePanel", void 0);
|
|
3961
|
+
__decorate([
|
|
3962
|
+
state()
|
|
3963
|
+
], ZenttoReportDesigner.prototype, "_relationEditorOpen", void 0);
|
|
3964
|
+
__decorate([
|
|
3965
|
+
state()
|
|
3966
|
+
], ZenttoReportDesigner.prototype, "_editingRelation", void 0);
|
|
3829
3967
|
__decorate([
|
|
3830
3968
|
state()
|
|
3831
3969
|
], ZenttoReportDesigner.prototype, "_undoStack", void 0);
|