@bagelink/vue 1.4.139 → 1.4.145
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/components/Btn.vue.d.ts.map +1 -1
- package/dist/components/Carousel.vue.d.ts +1 -1
- package/dist/components/Modal.vue.d.ts +3 -0
- package/dist/components/Modal.vue.d.ts.map +1 -1
- package/dist/components/Slider.vue.d.ts +1 -1
- package/dist/components/Slider.vue.d.ts.map +1 -1
- package/dist/components/analytics/BarChart.vue.d.ts +11 -3
- package/dist/components/analytics/BarChart.vue.d.ts.map +1 -1
- package/dist/components/analytics/LineChart.vue.d.ts +9 -0
- package/dist/components/analytics/LineChart.vue.d.ts.map +1 -1
- package/dist/components/analytics/PieChart.vue.d.ts +30 -2
- package/dist/components/analytics/PieChart.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/components/EditorToolbar.vue.d.ts +8 -0
- package/dist/components/form/inputs/RichText/components/EditorToolbar.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/components/TableGridSelector.vue.d.ts +9 -0
- package/dist/components/form/inputs/RichText/components/TableGridSelector.vue.d.ts.map +1 -0
- package/dist/components/form/inputs/RichText/composables/useCommands.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/composables/useEditor.d.ts +0 -14
- package/dist/components/form/inputs/RichText/composables/useEditor.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/composables/useEditorKeyboard.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/config.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/index.vue.d.ts +15 -15
- package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/richTextTypes.d.ts +1 -3
- package/dist/components/form/inputs/RichText/richTextTypes.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/commands.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/formatting.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/media-clean.d.ts +2 -0
- package/dist/components/form/inputs/RichText/utils/media-clean.d.ts.map +1 -0
- package/dist/components/form/inputs/RichText/utils/media.d.ts +4 -4
- package/dist/components/form/inputs/RichText/utils/media.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/selection.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/table.d.ts +1 -1
- package/dist/components/form/inputs/RichText/utils/table.d.ts.map +1 -1
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/layout/AppContent.vue.d.ts.map +1 -1
- package/dist/components/layout/AppLayout.vue.d.ts.map +1 -1
- package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -1
- package/dist/index.cjs +123 -22
- package/dist/index.mjs +123 -22
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/Btn.vue +50 -42
- package/src/components/Modal.vue +49 -50
- package/src/components/analytics/BarChart.vue +118 -7
- package/src/components/analytics/KpiCard.vue +2 -2
- package/src/components/analytics/LineChart.vue +189 -105
- package/src/components/analytics/PieChart.vue +392 -49
- package/src/components/dataTable/DataTable.vue +1 -1
- package/src/components/form/inputs/RichText/CheckList.md +23 -0
- package/src/components/form/inputs/RichText/components/EditorToolbar.vue +243 -27
- package/src/components/form/inputs/RichText/components/TableGridSelector.vue +94 -0
- package/src/components/form/inputs/RichText/composables/useCommands.ts +45 -0
- package/src/components/form/inputs/RichText/composables/useEditor.ts +13 -10
- package/src/components/form/inputs/RichText/composables/useEditorKeyboard.ts +3 -128
- package/src/components/form/inputs/RichText/config.ts +33 -10
- package/src/components/form/inputs/RichText/editor.css +300 -33
- package/src/components/form/inputs/RichText/index.vue +3271 -130
- package/src/components/form/inputs/RichText/richTextTypes.ts +7 -3
- package/src/components/form/inputs/RichText/utils/commands.ts +851 -90
- package/src/components/form/inputs/RichText/utils/formatting.ts +17 -15
- package/src/components/form/inputs/RichText/utils/media-clean.ts +0 -0
- package/src/components/form/inputs/RichText/utils/media.ts +133 -67
- package/src/components/form/inputs/RichText/utils/selection.ts +40 -11
- package/src/components/form/inputs/RichText/utils/table.ts +1 -1
- package/src/components/index.ts +1 -0
- package/src/components/layout/AppContent.vue +26 -26
- package/src/components/layout/AppLayout.vue +21 -3
- package/src/components/layout/AppSidebar.vue +5 -2
- package/src/styles/layout.css +267 -0
- package/src/styles/mobilLayout.css +266 -0
- package/src/styles/modal.css +3 -17
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { computed, ref, onMounted, onUnmounted } from 'vue'
|
|
2
3
|
import { Icon } from '@bagelink/vue'
|
|
3
|
-
import { computed } from 'vue'
|
|
4
4
|
|
|
5
5
|
interface PieData {
|
|
6
6
|
label: string
|
|
@@ -13,9 +13,22 @@ interface Props {
|
|
|
13
13
|
title?: string
|
|
14
14
|
icon?: string
|
|
15
15
|
color?: string
|
|
16
|
+
colors?: string[]
|
|
16
17
|
size?: number
|
|
17
18
|
showLegend?: boolean
|
|
18
19
|
donut?: boolean
|
|
20
|
+
thickness?: number
|
|
21
|
+
centerLabel?: string
|
|
22
|
+
animated?: boolean
|
|
23
|
+
animationDuration?: number
|
|
24
|
+
animationDelay?: number
|
|
25
|
+
animationStartDelay?: number
|
|
26
|
+
centerValue?: number
|
|
27
|
+
showCenterTotal?: boolean
|
|
28
|
+
percentageChange?: number
|
|
29
|
+
showLabelsOnChart?: boolean
|
|
30
|
+
showConnectorLines?: boolean
|
|
31
|
+
labelColor?: string
|
|
19
32
|
}
|
|
20
33
|
|
|
21
34
|
const props = withDefaults(defineProps<Props>(), {
|
|
@@ -24,52 +37,269 @@ const props = withDefaults(defineProps<Props>(), {
|
|
|
24
37
|
color: '#3B82F6',
|
|
25
38
|
size: 200,
|
|
26
39
|
showLegend: true,
|
|
27
|
-
donut: false
|
|
40
|
+
donut: false,
|
|
41
|
+
thickness: 0.6,
|
|
42
|
+
animated: true,
|
|
43
|
+
animationDuration: 1500,
|
|
44
|
+
animationDelay: 200,
|
|
45
|
+
animationStartDelay: 0,
|
|
46
|
+
centerLabel: 'Total',
|
|
47
|
+
showCenterTotal: true,
|
|
48
|
+
percentageChange: 0,
|
|
49
|
+
showLabelsOnChart: false,
|
|
50
|
+
showConnectorLines: false,
|
|
51
|
+
labelColor: '#333'
|
|
28
52
|
})
|
|
29
53
|
|
|
30
|
-
// Generate colors with different opacity based on the main color
|
|
54
|
+
// Generate colors with different opacity based on the main color OR use custom colors
|
|
31
55
|
const colors = computed(() => {
|
|
56
|
+
// If custom colors array is provided, use it
|
|
57
|
+
if (props.colors && props.colors.length > 0) {
|
|
58
|
+
return props.colors
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Otherwise, use the existing logic with opacity variations
|
|
32
62
|
const baseColor = props.color
|
|
33
63
|
const opacities = [1, 0.8, 0.6, 0.4, 0.85, 0.7, 0.5, 0.3, 0.9, 0.75]
|
|
34
64
|
|
|
35
|
-
return opacities.map(
|
|
65
|
+
return opacities.map(opacity => {
|
|
36
66
|
// Convert hex to rgba with opacity
|
|
37
67
|
const hex = baseColor.replace('#', '')
|
|
38
|
-
const r =
|
|
39
|
-
const g =
|
|
40
|
-
const b =
|
|
68
|
+
const r = parseInt(hex.substr(0, 2), 16)
|
|
69
|
+
const g = parseInt(hex.substr(2, 2), 16)
|
|
70
|
+
const b = parseInt(hex.substr(4, 2), 16)
|
|
41
71
|
|
|
42
72
|
return `rgba(${r}, ${g}, ${b}, ${opacity})`
|
|
43
73
|
})
|
|
44
74
|
})
|
|
45
75
|
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
76
|
+
// Animation state
|
|
77
|
+
const animatedProgress = ref(0)
|
|
78
|
+
const isAnimating = ref(false)
|
|
79
|
+
const chartRef = ref<HTMLElement>()
|
|
80
|
+
const isInView = ref(false)
|
|
81
|
+
|
|
82
|
+
onMounted(() => {
|
|
83
|
+
if (props.animated) {
|
|
84
|
+
setupIntersectionObserver()
|
|
85
|
+
} else {
|
|
86
|
+
animatedProgress.value = 1
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
onUnmounted(() => {
|
|
91
|
+
if (observer) {
|
|
92
|
+
observer.disconnect()
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
let observer: IntersectionObserver | null = null
|
|
97
|
+
|
|
98
|
+
const setupIntersectionObserver = () => {
|
|
99
|
+
if (typeof window === 'undefined') return
|
|
100
|
+
|
|
101
|
+
observer = new IntersectionObserver(
|
|
102
|
+
(entries) => {
|
|
103
|
+
entries.forEach((entry) => {
|
|
104
|
+
if (entry.isIntersecting && !isInView.value) {
|
|
105
|
+
isInView.value = true
|
|
106
|
+
// Add start delay + small delay to ensure the component is fully rendered
|
|
107
|
+
setTimeout(() => {
|
|
108
|
+
startAnimation()
|
|
109
|
+
}, 100 + props.animationStartDelay)
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
threshold: 0.3, // Start animation when 30% of the component is visible
|
|
115
|
+
rootMargin: '50px'
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
// Use nextTick to ensure chartRef is available
|
|
120
|
+
if (chartRef.value) {
|
|
121
|
+
observer.observe(chartRef.value)
|
|
122
|
+
} else {
|
|
123
|
+
// Retry after a short delay if ref is not ready
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
if (chartRef.value && observer) {
|
|
126
|
+
observer.observe(chartRef.value)
|
|
127
|
+
}
|
|
128
|
+
}, 100)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const startAnimation = () => {
|
|
133
|
+
const delayType = props.animationDelay > 0 ? "staggered with delay" : "quick stagger"
|
|
134
|
+
console.log(`Starting animation for "${props.title}" - ${delayType} mode (duration: ${props.animationDuration}ms, start delay: ${props.animationStartDelay}ms)`)
|
|
135
|
+
isAnimating.value = true
|
|
136
|
+
animatedProgress.value = 0
|
|
137
|
+
|
|
138
|
+
const startTime = Date.now()
|
|
139
|
+
|
|
140
|
+
const animate = () => {
|
|
141
|
+
const elapsed = Date.now() - startTime
|
|
142
|
+
const progress = Math.min(elapsed / props.animationDuration, 1)
|
|
143
|
+
|
|
144
|
+
// Easing function for smooth animation
|
|
145
|
+
animatedProgress.value = easeOutCubic(progress)
|
|
146
|
+
|
|
147
|
+
if (progress < 1) {
|
|
148
|
+
requestAnimationFrame(animate)
|
|
149
|
+
} else {
|
|
150
|
+
isAnimating.value = false
|
|
151
|
+
console.log('Animation completed')
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
requestAnimationFrame(animate)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const easeOutCubic = (t: number): number => {
|
|
159
|
+
return 1 - Math.pow(1 - t, 3)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Expose animation control methods
|
|
163
|
+
const restartAnimation = () => {
|
|
164
|
+
if (props.animated) {
|
|
165
|
+
startAnimation()
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
defineExpose({
|
|
170
|
+
restartAnimation
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
// Calculate SVG size based on connector lines
|
|
174
|
+
const svgSize = computed(() => {
|
|
175
|
+
if (props.showConnectorLines) {
|
|
176
|
+
return {
|
|
177
|
+
width: props.size + 200, // Increased even more
|
|
178
|
+
height: props.size + 100 // Increased even more
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
width: props.size,
|
|
183
|
+
height: props.size
|
|
184
|
+
}
|
|
185
|
+
})
|
|
53
186
|
|
|
54
|
-
|
|
187
|
+
// Calculate center offset for connector lines
|
|
188
|
+
const centerOffset = computed(() => {
|
|
189
|
+
if (props.showConnectorLines) {
|
|
190
|
+
return {
|
|
191
|
+
x: svgSize.value.width / 2 - 10, // Less offset
|
|
192
|
+
y: svgSize.value.height / 2
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
x: props.size / 2,
|
|
197
|
+
y: props.size / 2
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
const totalValue = computed(() =>
|
|
202
|
+
props.data.reduce((sum, item) => sum + item.value, 0)
|
|
55
203
|
)
|
|
56
204
|
|
|
205
|
+
// Calculate label positions for segments
|
|
206
|
+
const getLabelPosition = (segment: any) => {
|
|
207
|
+
const midAngle = (segment.startAngle + segment.endAngle) / 2
|
|
208
|
+
const midAngleRad = (midAngle * Math.PI) / 180
|
|
209
|
+
const radius = props.size / 2 - 10
|
|
210
|
+
const labelRadius = props.donut ? radius * 0.8 : radius * 0.7
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
x: Math.cos(midAngleRad) * labelRadius,
|
|
214
|
+
y: Math.sin(midAngleRad) * labelRadius
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Calculate connector line positions
|
|
219
|
+
const getConnectorLine = (segment: any) => {
|
|
220
|
+
const midAngle = (segment.startAngle + segment.endAngle) / 2
|
|
221
|
+
const midAngleRad = (midAngle * Math.PI) / 180
|
|
222
|
+
const radius = props.size / 2 - 10
|
|
223
|
+
const innerRadius = props.donut ? radius * props.thickness : radius * 0.5
|
|
224
|
+
const outerRadius = radius + 15 // Reduced from 20 to 15
|
|
225
|
+
|
|
226
|
+
const innerX = Math.cos(midAngleRad) * innerRadius
|
|
227
|
+
const innerY = Math.sin(midAngleRad) * innerRadius
|
|
228
|
+
const outerX = Math.cos(midAngleRad) * outerRadius
|
|
229
|
+
const outerY = Math.sin(midAngleRad) * outerRadius
|
|
230
|
+
|
|
231
|
+
// Extended line for label - reduced extension
|
|
232
|
+
const labelX = outerX + (outerX > 0 ? 8 : -8) // Reduced from 10 to 8
|
|
233
|
+
const labelY = outerY
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
innerX,
|
|
237
|
+
innerY,
|
|
238
|
+
outerX,
|
|
239
|
+
outerY,
|
|
240
|
+
labelX,
|
|
241
|
+
labelY,
|
|
242
|
+
textAnchor: outerX > 0 ? 'start' : 'end'
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const centerDisplayValue = computed(() => {
|
|
247
|
+
if (props.centerValue !== undefined) {
|
|
248
|
+
return props.centerValue
|
|
249
|
+
}
|
|
250
|
+
return props.showCenterTotal ? totalValue.value : 0
|
|
251
|
+
})
|
|
252
|
+
|
|
57
253
|
const pieSegments = computed(() => {
|
|
58
254
|
let cumulativeAngle = 0
|
|
59
255
|
return props.data.map((item, index) => {
|
|
60
256
|
const percentage = item.value / totalValue.value
|
|
61
257
|
const angle = percentage * 360
|
|
258
|
+
|
|
62
259
|
const startAngle = cumulativeAngle
|
|
63
260
|
const endAngle = cumulativeAngle + angle
|
|
64
261
|
cumulativeAngle += angle
|
|
65
262
|
|
|
263
|
+
// Calculate if this segment should be visible based on animation progress
|
|
264
|
+
// Add a small delay even for the first segment to avoid immediate appearance
|
|
265
|
+
const baseDelay = 100 // 100ms delay for the first segment
|
|
266
|
+
const segmentDelay = props.animationDelay > 0 ?
|
|
267
|
+
baseDelay + (index * props.animationDelay) :
|
|
268
|
+
baseDelay + (index * 50) // Small stagger even when animationDelay is 0
|
|
269
|
+
const totalElapsedTime = animatedProgress.value * props.animationDuration
|
|
270
|
+
const isVisible = !props.animated || totalElapsedTime >= segmentDelay
|
|
271
|
+
|
|
272
|
+
// Debug log for animation
|
|
273
|
+
if (props.animated && index === 0) {
|
|
274
|
+
console.log(`Animation progress: ${(animatedProgress.value * 100).toFixed(1)}%, elapsed: ${totalElapsedTime.toFixed(0)}ms`)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (!isVisible) {
|
|
278
|
+
if (index < 3) {
|
|
279
|
+
console.log(`Segment ${index} waiting - needs ${segmentDelay}ms, current: ${totalElapsedTime.toFixed(0)}ms`)
|
|
280
|
+
}
|
|
281
|
+
return {
|
|
282
|
+
...item,
|
|
283
|
+
percentage,
|
|
284
|
+
angle: 0,
|
|
285
|
+
startAngle,
|
|
286
|
+
endAngle: startAngle,
|
|
287
|
+
path: '',
|
|
288
|
+
color: item.color || colors.value[index % colors.value.length],
|
|
289
|
+
visible: false
|
|
290
|
+
}
|
|
291
|
+
} else if (index < 3) {
|
|
292
|
+
console.log(`Segment ${index} visible! - delay: ${segmentDelay}ms, elapsed: ${totalElapsedTime.toFixed(0)}ms`)
|
|
293
|
+
}
|
|
294
|
+
|
|
66
295
|
const startAngleRad = (startAngle * Math.PI) / 180
|
|
67
296
|
const endAngleRad = (endAngle * Math.PI) / 180
|
|
68
297
|
|
|
69
298
|
const radius = props.size / 2 - 10
|
|
70
|
-
const innerRadius = props.donut ? radius *
|
|
299
|
+
const innerRadius = props.donut ? radius * props.thickness : 0
|
|
71
300
|
|
|
72
|
-
const
|
|
301
|
+
const segmentAngle = endAngle - startAngle
|
|
302
|
+
const largeArcFlag = segmentAngle > 180 ? 1 : 0
|
|
73
303
|
|
|
74
304
|
const pathData = props.donut
|
|
75
305
|
? createDonutPath(startAngleRad, endAngleRad, radius, innerRadius, largeArcFlag)
|
|
@@ -78,11 +308,12 @@ const pieSegments = computed(() => {
|
|
|
78
308
|
return {
|
|
79
309
|
...item,
|
|
80
310
|
percentage,
|
|
81
|
-
angle,
|
|
311
|
+
angle: segmentAngle,
|
|
82
312
|
startAngle,
|
|
83
313
|
endAngle,
|
|
84
|
-
pathData,
|
|
85
|
-
color: item.color || colors.value[index % colors.value.length]
|
|
314
|
+
path: pathData,
|
|
315
|
+
color: item.color || colors.value[index % colors.value.length],
|
|
316
|
+
visible: true
|
|
86
317
|
}
|
|
87
318
|
})
|
|
88
319
|
})
|
|
@@ -117,47 +348,112 @@ function formatValue(value: number): string {
|
|
|
117
348
|
function formatPercentage(percentage: number): string {
|
|
118
349
|
return `${(percentage * 100).toFixed(1)}%`
|
|
119
350
|
}
|
|
351
|
+
|
|
352
|
+
function formatTooltip(segment: any): string {
|
|
353
|
+
return `<div class="pieTooltip">
|
|
354
|
+
<div class="round w-10px h-10px w-100p inline-block" style="background-color: ${segment.color};"></div>
|
|
355
|
+
<p class="inline-block txt12">${segment.label}</p>
|
|
356
|
+
<div class="gap-025 flex">
|
|
357
|
+
<p class="bold txt18">${formatValue(segment.value)}</p>
|
|
358
|
+
<p>|</p>
|
|
359
|
+
<p class="bold txt18">${formatPercentage(segment.percentage)}</p></div>`
|
|
360
|
+
}
|
|
120
361
|
</script>
|
|
121
362
|
|
|
122
363
|
<template>
|
|
123
|
-
<div class="h-100p flex column flex-stretch">
|
|
124
|
-
<div class="flex space-between">
|
|
125
|
-
<div class="flex align-center gap-05
|
|
126
|
-
<Icon :name="icon" size="1.2" :color="color" class="line-height-
|
|
364
|
+
<div ref="chartRef" class="h-100p flex column flex-stretch">
|
|
365
|
+
<div class="flex space-between pb-1">
|
|
366
|
+
<div class="flex align-center gap-05">
|
|
367
|
+
<Icon :name="icon" size="1.2" :color="color" class="line-height-0" />
|
|
127
368
|
<p class="white-space light m_txt14">
|
|
128
369
|
{{ title }}
|
|
129
370
|
</p>
|
|
130
371
|
</div>
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
372
|
+
<div v-if="percentageChange !== 0" class="flex align-center gap-025">
|
|
373
|
+
<Icon class="line-height-1" :name="percentageChange > 0 ? 'trending_up' : 'trending_down'" size="1" :class="percentageChange > 0 ? 'color-success' : 'color-danger'" />
|
|
374
|
+
<span class="txt12 bold" :class="percentageChange > 0 ? 'color-success' : 'color-danger'">
|
|
375
|
+
{{ Math.abs(percentageChange) }}%
|
|
376
|
+
</span>
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
<div class="chart-container flex space-between gap-1 flex-wrap flex-grow" :class="{ 'with-legend': showLegend, 'with-connectors': showConnectorLines }">
|
|
380
|
+
<svg :width="svgSize.width" :height="svgSize.height" class="chart-svg mx-auto flex-shrink-0" :style="showConnectorLines ? 'overflow: visible;' : ''">
|
|
381
|
+
<g :transform="`translate(${centerOffset.x}, ${centerOffset.y})`">
|
|
382
|
+
<path v-for="(segment, index) in pieSegments.filter(s => s.visible)" :key="index" v-tooltip="{ content: formatTooltip(segment), html: true }" :d="segment.path" :fill="segment.color"
|
|
383
|
+
class="pie-segment" stroke="white" stroke-width="2" />
|
|
384
|
+
|
|
385
|
+
<!-- Center text for donut chart -->
|
|
386
|
+
<g v-if="donut && (showCenterTotal || centerValue !== undefined)" class="center-text">
|
|
387
|
+
<text x="0" y="-8" text-anchor="middle" dominant-baseline="middle" class="center-label txt12 opacity-7">
|
|
388
|
+
{{ centerLabel }}
|
|
389
|
+
</text>
|
|
390
|
+
<text x="0" y="12" text-anchor="middle" dominant-baseline="middle" class="center-value txt20 bold">
|
|
391
|
+
{{ formatValue(centerDisplayValue) }}
|
|
392
|
+
</text>
|
|
393
|
+
</g>
|
|
394
|
+
|
|
395
|
+
<!-- Connector lines -->
|
|
396
|
+
<g v-if="showConnectorLines" class="connector-lines">
|
|
397
|
+
<g v-for="(segment, index) in pieSegments.filter(s => s.visible)" :key="`line-${index}`">
|
|
398
|
+
<polyline
|
|
399
|
+
:points="`${getConnectorLine(segment).innerX},${getConnectorLine(segment).innerY} ${getConnectorLine(segment).outerX},${getConnectorLine(segment).outerY} ${getConnectorLine(segment).labelX},${getConnectorLine(segment).labelY}`"
|
|
400
|
+
stroke="#333" stroke-width="1.5" fill="none" />
|
|
401
|
+
</g>
|
|
402
|
+
</g>
|
|
403
|
+
|
|
404
|
+
<!-- Labels on chart -->
|
|
405
|
+
<g v-if="showLabelsOnChart" class="chart-labels">
|
|
406
|
+
<!-- Labels without connector lines -->
|
|
407
|
+
<g v-if="!showConnectorLines">
|
|
408
|
+
<text v-for="(segment, index) in pieSegments.filter(s => s.visible)" :key="`label-${index}`" :x="getLabelPosition(segment).x" :y="getLabelPosition(segment).y - 5"
|
|
409
|
+
text-anchor="middle" dominant-baseline="middle" class="chart-label txt12 pointer-events-none" :fill="labelColor">
|
|
410
|
+
{{ segment.label }}
|
|
411
|
+
</text>
|
|
412
|
+
<text v-for="(segment, index) in pieSegments.filter(s => s.visible)" :key="`value-${index}`" :x="getLabelPosition(segment).x" :y="getLabelPosition(segment).y + 12"
|
|
413
|
+
text-anchor="middle" dominant-baseline="middle" class="chart-value txt16 mt-1 line-height-2 bold pointer-events-none" :fill="labelColor">
|
|
414
|
+
{{ formatValue(segment.value) }}
|
|
415
|
+
</text>
|
|
416
|
+
</g>
|
|
417
|
+
|
|
418
|
+
<!-- Labels with connector lines -->
|
|
419
|
+
<text v-if="showConnectorLines" v-for="(segment, index) in pieSegments.filter(s => s.visible)" :key="`label-line-${index}`" :x="getConnectorLine(segment).labelX"
|
|
420
|
+
:y="getConnectorLine(segment).labelY - 8" :text-anchor="getConnectorLine(segment).textAnchor" dominant-baseline="middle" class="chart-label txt12 bold" :fill="labelColor">
|
|
421
|
+
{{ segment.label }}
|
|
422
|
+
</text>
|
|
423
|
+
|
|
424
|
+
<!-- Value labels with connector lines -->
|
|
425
|
+
<text v-if="showConnectorLines" v-for="(segment, index) in pieSegments.filter(s => s.visible)" :key="`value-line-${index}`" :x="getConnectorLine(segment).labelX"
|
|
426
|
+
:y="getConnectorLine(segment).labelY + 2" :text-anchor="getConnectorLine(segment).textAnchor" dominant-baseline="middle" class="chart-value txt11" fill="#666">
|
|
427
|
+
{{ formatValue(segment.value) }}
|
|
428
|
+
</text>
|
|
429
|
+
|
|
430
|
+
<!-- Percentage labels with connector lines -->
|
|
431
|
+
<text v-if="showConnectorLines" v-for="(segment, index) in pieSegments.filter(s => s.visible)" :key="`percentage-${index}`" :x="getConnectorLine(segment).labelX"
|
|
432
|
+
:y="getConnectorLine(segment).labelY + 15" :text-anchor="getConnectorLine(segment).textAnchor" dominant-baseline="middle" class="chart-percentage txt10 pointer-events-none"
|
|
433
|
+
fill="#999">
|
|
434
|
+
({{ formatPercentage(segment.percentage) }})
|
|
435
|
+
</text>
|
|
436
|
+
</g>
|
|
437
|
+
</g>
|
|
438
|
+
</svg>
|
|
439
|
+
|
|
440
|
+
<div v-if="showLegend" class="legend mx-auto display-flex column gap-05 min-w-100px">
|
|
441
|
+
<div v-for="(segment, index) in pieSegments" :key="index" class="gap-1 flex align-items-center txt14 justify-content-start">
|
|
442
|
+
<div class="legend-color flex-shrink-0 radius-05" :style="{ backgroundColor: segment.color }" />
|
|
443
|
+
<span v-if="segment.label" class="flex-shrink-1 flex-grow-1">{{ segment.label }}</span>
|
|
444
|
+
<span class="bold">{{ formatValue(segment.value) }}</span>
|
|
445
|
+
<span class="opacity-6 txt12">({{ formatPercentage(segment.percentage) }})</span>
|
|
446
|
+
</div>
|
|
154
447
|
</div>
|
|
155
448
|
</div>
|
|
156
|
-
|
|
157
|
-
</div>
|
|
449
|
+
</div>
|
|
158
450
|
</template>
|
|
159
451
|
|
|
160
452
|
<style scoped>
|
|
453
|
+
.pie-segment {
|
|
454
|
+
transition: opacity 0.3s ease;
|
|
455
|
+
}
|
|
456
|
+
|
|
161
457
|
[dir="rtl"] .chart-container.with-legend {
|
|
162
458
|
flex-direction: row-reverse;
|
|
163
459
|
}
|
|
@@ -165,6 +461,17 @@ function formatPercentage(percentage: number): string {
|
|
|
165
461
|
.pie-segment {
|
|
166
462
|
cursor: pointer;
|
|
167
463
|
transition: opacity 0.2s ease;
|
|
464
|
+
animation: fadeIn 0.3s ease-in-out;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
@keyframes fadeIn {
|
|
468
|
+
from {
|
|
469
|
+
opacity: 0;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
to {
|
|
473
|
+
opacity: 1;
|
|
474
|
+
}
|
|
168
475
|
}
|
|
169
476
|
|
|
170
477
|
.pie-segment:hover {
|
|
@@ -180,4 +487,40 @@ function formatPercentage(percentage: number): string {
|
|
|
180
487
|
width: 12px;
|
|
181
488
|
height: 12px;
|
|
182
489
|
}
|
|
490
|
+
|
|
491
|
+
.connector-lines polyline {
|
|
492
|
+
opacity: 0.8;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.chart-container.with-connectors {
|
|
496
|
+
overflow: visible;
|
|
497
|
+
padding: 10px;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
.chart-container.with-connectors .chart-svg {
|
|
501
|
+
overflow: visible;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
.chart-svg {
|
|
505
|
+
max-width: 100%;
|
|
506
|
+
max-height: 100%;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
.chart-container {
|
|
510
|
+
min-height: 0;
|
|
511
|
+
align-items: center;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.chart-container.flex-grow {
|
|
515
|
+
flex: 1;
|
|
516
|
+
}
|
|
517
|
+
</style>
|
|
518
|
+
|
|
519
|
+
<style>
|
|
520
|
+
.v-popper--theme-tooltip .v-popper__inner:has(.pieTooltip) {
|
|
521
|
+
background-color: var(--bgl-black) !important;
|
|
522
|
+
color: var(--bgl-white) !important;
|
|
523
|
+
border-radius: 0.5rem !important;
|
|
524
|
+
padding: 0.25rem 0.5rem !important;
|
|
525
|
+
}
|
|
183
526
|
</style>
|
|
@@ -127,7 +127,7 @@ const showLoading = computed(() => loading.value)
|
|
|
127
127
|
@click="toggleSort(field?.id || '')"
|
|
128
128
|
>
|
|
129
129
|
<div class="flex">
|
|
130
|
-
{{ field.label || keyToLabel(field?.id) }}
|
|
130
|
+
{{ field.label || field.attrs?.label || keyToLabel(field?.id) }}
|
|
131
131
|
<div class="list-arrows" :class="{ sorted: sortField === field?.id }">
|
|
132
132
|
<Icon :class="{ desc: sortDirection === 'DESC' }" icon="keyboard_arrow_up" />
|
|
133
133
|
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[] that h1-6 works
|
|
2
|
+
[] enter after h1-6 turns back to p (text)
|
|
3
|
+
[] bold/underline/italic works both in the toolbar, and in the mini tow bar
|
|
4
|
+
[] bold/underline/italic get removed after clicking again
|
|
5
|
+
[] add and remove bold/underline/italic when the cursor it is on the end of the word
|
|
6
|
+
[] list works properly meaning one enter create a new item two enters takes you out of the list
|
|
7
|
+
[] alignments still works
|
|
8
|
+
[] list change direction
|
|
9
|
+
[] undo/redo still works
|
|
10
|
+
[] clear formating removes all inline style
|
|
11
|
+
[] pop-ups work - insert link - insert image - insert video - insert embed
|
|
12
|
+
[] That link actually being inserted
|
|
13
|
+
[] That image is actually being inserted
|
|
14
|
+
[] That video is actually being inserted
|
|
15
|
+
[] That embed is actually being inserted
|
|
16
|
+
[] able to insert table
|
|
17
|
+
[] advance options in table opens popup
|
|
18
|
+
[] edit btn is visible clickable, especially if you have more than one table
|
|
19
|
+
[] the table can get alignment and direction, even if it's the only thing in the body
|
|
20
|
+
[] that you can insert a table to the table edit btn
|
|
21
|
+
[] you can change table dircation and alignment
|
|
22
|
+
[] make sure you can change direction does the first element in the table
|
|
23
|
+
[] that table get the Direct direction and alignment relative to the rest of the text
|