40cffb3beb
TaxBaik CI/CD / build-and-deploy (push) Successful in 2m26s
Problem: JavaScript login form saved tokens to localStorage but didn't notify CustomAuthenticationStateProvider, causing [Authorize] pages to remain in 'loading' state indefinitely. The provider only reads tokens when: 1. GetAuthenticationStateAsync() is called (page load) 2. NotifyAuthenticationStateChanged() is triggered (UI updates) But JavaScript login didn't trigger either, leaving the authentication state stale. Solution: Convert AdminLoginForm from HTML+JavaScript to pure Blazor component. Now the login flow is: 1. User enters credentials in Blazor form 2. HttpClient POST to /api/auth/login 3. Save tokens to localStorage 4. Call CustomAuthenticationStateProvider.LoginAsync() directly 5. Blazor detects auth state change and re-evaluates [Authorize] pages 6. Dashboard [Authorize] page renders successfully Result: Immediate authentication state update, no loading timeout on protected pages. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
339 lines
11 KiB
JavaScript
339 lines
11 KiB
JavaScript
/**
|
|
* Easy Pie Chart Implementation
|
|
* Vanilla JavaScript implementation for easy pie charts
|
|
*/
|
|
|
|
|
|
// Global registry to keep track of all chart instances
|
|
window.easyPieChartRegistry = {
|
|
instances: [],
|
|
elements: []
|
|
};
|
|
|
|
// Global color state
|
|
window.easyPieChartColors = {
|
|
currentColors: {},
|
|
previousColors: {}
|
|
};
|
|
|
|
// Extract colors from the theme
|
|
function extractEasyPieChartColors() {
|
|
// Store previous colors
|
|
window.easyPieChartColors.previousColors = {...window.easyPieChartColors.currentColors};
|
|
|
|
// Extract new colors
|
|
window.easyPieChartColors.currentColors = {};
|
|
|
|
if (window.colorMap) {
|
|
// Define color categories to extract
|
|
const colorCategories = ['primary', 'success', 'warning', 'danger', 'info'];
|
|
|
|
// Get all available color shades for each category
|
|
colorCategories.forEach(category => {
|
|
if (window.colorMap[category]) {
|
|
// For each category, find all available shades
|
|
Object.keys(window.colorMap[category]).forEach(shade => {
|
|
if (window.colorMap[category][shade]?.hex) {
|
|
// Store each shade with a category-shade key
|
|
const colorKey = `${category}-${shade}`;
|
|
window.easyPieChartColors.currentColors[colorKey] = window.colorMap[category][shade].hex;
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
// Also add bodyColor as it's commonly used
|
|
if (window.colorMap.bootstrapVars?.bodyColor?.hex) {
|
|
window.easyPieChartColors.currentColors['bodyColor'] = window.colorMap.bootstrapVars.bodyColor.hex;
|
|
window.easyPieChartColors.currentColors['bodyColor-alpha'] = window.colorMap.bootstrapVars.bodyColor.rgba(0.07);
|
|
}
|
|
}
|
|
|
|
return window.easyPieChartColors.currentColors;
|
|
}
|
|
|
|
// Helper to update a color value based on the before/after color mapping
|
|
function updateEasyPieChartColorValue(color) {
|
|
if (!color || typeof color !== 'string') return color;
|
|
|
|
// First, direct match from previous colors to current colors
|
|
if (window.easyPieChartColors.previousColors) {
|
|
for (const [key, oldColor] of Object.entries(window.easyPieChartColors.previousColors)) {
|
|
if (color === oldColor) {
|
|
return window.easyPieChartColors.currentColors[key] || color;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If no direct match, check if it's a color from the theme
|
|
if (window.colorMap) {
|
|
const colorCategories = ['primary', 'success', 'warning', 'danger', 'info'];
|
|
|
|
// Try to match with any shade in each category
|
|
for (const category of colorCategories) {
|
|
if (window.colorMap[category]) {
|
|
for (const shade in window.colorMap[category]) {
|
|
if (window.colorMap[category][shade]?.hex === color) {
|
|
// We found a match - use the updated color for this category/shade
|
|
return window.colorMap[category][shade].hex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handle rgba colors
|
|
if (color.includes('rgba') && window.colorMap?.bootstrapVars?.bodyColor) {
|
|
const opacityMatch = color.match(/rgba\([^)]+,\s*([\d.]+)\)/);
|
|
if (opacityMatch && opacityMatch[1]) {
|
|
const opacity = parseFloat(opacityMatch[1]);
|
|
|
|
// Common opacities to check against
|
|
const standardOpacities = [0.07, 0.1, 0.2, 0.3, 0.5, 0.7, 0.8, 0.9];
|
|
const closestOpacity = standardOpacities.reduce((prev, curr) =>
|
|
Math.abs(curr - opacity) < Math.abs(prev - opacity) ? curr : prev
|
|
);
|
|
|
|
// If this is a very close match to a standard opacity, use exact value
|
|
if (Math.abs(closestOpacity - opacity) < 0.02) {
|
|
return window.colorMap.bootstrapVars.bodyColor.rgba(closestOpacity);
|
|
}
|
|
|
|
// Otherwise use the exact opacity provided
|
|
return window.colorMap.bootstrapVars.bodyColor.rgba(opacity);
|
|
}
|
|
}
|
|
|
|
// Handle CSS variables
|
|
if (color.startsWith('var(')) {
|
|
// Try to extract the variable value
|
|
const varName = color.replace('var(', '').replace(')', '').trim();
|
|
const computedStyle = getComputedStyle(document.documentElement);
|
|
const varValue = computedStyle.getPropertyValue(varName);
|
|
if (varValue) {
|
|
return varValue.trim();
|
|
}
|
|
}
|
|
|
|
return color;
|
|
}
|
|
|
|
// Global function to update all Easy Pie Chart colors
|
|
window.updateEasyPieCharts = function() {
|
|
console.log(`EasyPieChart: Updating colors for ${window.easyPieChartRegistry.instances.length} charts`);
|
|
|
|
// Update color maps
|
|
extractEasyPieChartColors();
|
|
|
|
if (window.easyPieChartRegistry.instances.length === 0) {
|
|
console.warn('EasyPieChart: No charts found to update');
|
|
return;
|
|
}
|
|
|
|
// Update each chart with new colors
|
|
window.easyPieChartRegistry.instances.forEach((chart, index) => {
|
|
try {
|
|
if (!chart) {
|
|
console.warn(`EasyPieChart: Chart #${index + 1} cannot be updated`);
|
|
return;
|
|
}
|
|
|
|
const element = window.easyPieChartRegistry.elements[index];
|
|
if (!element) return;
|
|
|
|
// Get computed style
|
|
const computedStyle = window.getComputedStyle(element);
|
|
|
|
// Update barColor
|
|
const currentBarColor = chart.options.barColor;
|
|
const newBarColor = computedStyle.color || currentBarColor;
|
|
const updatedBarColor = updateEasyPieChartColorValue(newBarColor);
|
|
|
|
// Update trackColor
|
|
let updatedTrackColor;
|
|
try {
|
|
if (window.colorMap && window.colorMap.bootstrapVars && window.colorMap.bootstrapVars.bodyColorRgb) {
|
|
updatedTrackColor = window.colorMap.bootstrapVars.bodyColorRgb.rgba(0.07);
|
|
} else {
|
|
updatedTrackColor = 'rgba(0,0,0,0.04)';
|
|
}
|
|
} catch (e) {
|
|
updatedTrackColor = 'rgba(0,0,0,0.04)';
|
|
}
|
|
|
|
// Update scaleColor
|
|
const currentScaleColor = chart.options.scaleColor;
|
|
const newScaleColor = element.dataset.scalecolor || computedStyle.color || currentScaleColor;
|
|
const updatedScaleColor = updateEasyPieChartColorValue(newScaleColor);
|
|
|
|
// Apply the new colors using Canvas API's getImageData for efficiency
|
|
const canvas = chart.renderer.getCanvas();
|
|
const ctx = chart.renderer.getCtx();
|
|
|
|
// If canvas context is available, use pixel manipulation
|
|
if (ctx && ctx.getImageData && ctx.putImageData) {
|
|
// Store current canvas state
|
|
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
const data = imageData.data;
|
|
|
|
// Parse colors to RGB
|
|
const barRGB = parseColorToRGB(updatedBarColor);
|
|
const oldBarRGB = parseColorToRGB(currentBarColor);
|
|
const trackRGB = parseColorToRGB(updatedTrackColor);
|
|
|
|
// Update pixels - only change bar color (non-transparent, non-track pixels)
|
|
for (let i = 0; i < data.length; i += 4) {
|
|
// Only process non-transparent pixels
|
|
if (data[i + 3] > 0) {
|
|
// Check if pixel is close to the old bar color and not track color
|
|
const isBarPixel =
|
|
(Math.abs(data[i] - oldBarRGB.r) < 15 &&
|
|
Math.abs(data[i + 1] - oldBarRGB.g) < 15 &&
|
|
Math.abs(data[i + 2] - oldBarRGB.b) < 15) ||
|
|
(Math.abs(data[i] - trackRGB.r) > 10 ||
|
|
Math.abs(data[i + 1] - trackRGB.g) > 10 ||
|
|
Math.abs(data[i + 2] - trackRGB.b) > 10);
|
|
|
|
if (isBarPixel) {
|
|
// Update to new bar color
|
|
data[i] = barRGB.r;
|
|
data[i + 1] = barRGB.g;
|
|
data[i + 2] = barRGB.b;
|
|
// Keep original alpha
|
|
}
|
|
}
|
|
}
|
|
|
|
// Put the modified image data back
|
|
ctx.putImageData(imageData, 0, 0);
|
|
|
|
// Update the chart's options
|
|
chart.options.barColor = updatedBarColor;
|
|
chart.options.trackColor = updatedTrackColor;
|
|
chart.options.scaleColor = updatedScaleColor;
|
|
|
|
console.log(`EasyPieChart: Updated colors for chart #${index + 1} using pixel manipulation`);
|
|
} else {
|
|
// Fallback to standard update method
|
|
chart.options.barColor = updatedBarColor;
|
|
chart.options.trackColor = updatedTrackColor;
|
|
chart.options.scaleColor = updatedScaleColor;
|
|
|
|
// Redraw the chart with new colors
|
|
chart.update(chart.currentValue);
|
|
|
|
console.log(`EasyPieChart: Updated colors for chart #${index + 1} using redraw`);
|
|
}
|
|
} catch (e) {
|
|
console.error(`EasyPieChart: Error updating chart #${index + 1}:`, e);
|
|
}
|
|
});
|
|
|
|
console.log('EasyPieChart: Chart color update complete');
|
|
};
|
|
|
|
// Helper function to parse color string to RGB
|
|
function parseColorToRGB(color) {
|
|
// For hex colors
|
|
if (color.startsWith('#')) {
|
|
const hex = color.substring(1);
|
|
const bigint = parseInt(hex, 16);
|
|
return {
|
|
r: (bigint >> 16) & 255,
|
|
g: (bigint >> 8) & 255,
|
|
b: bigint & 255
|
|
};
|
|
}
|
|
// For rgb/rgba colors
|
|
else if (color.startsWith('rgb')) {
|
|
const matches = color.match(/(\d+),\s*(\d+),\s*(\d+)/);
|
|
if (matches) {
|
|
return {
|
|
r: parseInt(matches[1]),
|
|
g: parseInt(matches[2]),
|
|
b: parseInt(matches[3])
|
|
};
|
|
}
|
|
}
|
|
|
|
// Default fallback
|
|
return { r: 0, g: 0, b: 0 };
|
|
}
|
|
|
|
// Initialize colors on load
|
|
extractEasyPieChartColors();
|
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
|
|
/*
|
|
Browsers optimize canvas rendering for drawing, not for reading pixel data.
|
|
When you use getImageData() repeatedly (as some chart libraries do for animations or gradients),
|
|
it can cause readback performance hits.
|
|
|
|
https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Optimizing_canvas
|
|
*/
|
|
(function patchEasyPieCanvasContext() {
|
|
const originalGetContext = HTMLCanvasElement.prototype.getContext;
|
|
HTMLCanvasElement.prototype.getContext = function(type, options) {
|
|
if (type === '2d') {
|
|
options = { ...options, willReadFrequently: true };
|
|
}
|
|
return originalGetContext.call(this, type, options);
|
|
};
|
|
})();
|
|
|
|
/* Easy pie chart initialization
|
|
Pure JavaScript implementation with no jQuery dependencies
|
|
*/
|
|
document.querySelectorAll('.js-easy-pie-chart').forEach(function(element) {
|
|
// Get element properties using vanilla JS
|
|
const computedStyle = window.getComputedStyle(element);
|
|
const barcolor = computedStyle.color || 'var(--primary-700)';
|
|
|
|
// Check if window.colorMap exists, if not use a fallback
|
|
let trackcolor;
|
|
try {
|
|
if (window.colorMap && window.colorMap.bootstrapVars && window.colorMap.bootstrapVars.bodyColorRgb) {
|
|
trackcolor = window.colorMap.bootstrapVars.bodyColorRgb.rgba(0.07);
|
|
} else {
|
|
trackcolor = 'rgba(0,0,0,0.04)';
|
|
}
|
|
} catch (e) {
|
|
trackcolor = 'rgba(0,0,0,0.04)';
|
|
}
|
|
|
|
// Read dataset attributes with fallbacks
|
|
const size = parseInt(element.dataset.piesize) || 50;
|
|
const scalecolor = element.dataset.scalecolor || computedStyle.color;
|
|
const scalelength = parseInt(element.dataset.scalelength) || 0;
|
|
const linewidth = parseInt(element.dataset.linewidth) || parseInt(size / 8.5);
|
|
const linecap = element.dataset.linecap || 'butt'; // butt, round and square.
|
|
|
|
// Create EasyPieChart instance
|
|
const chart = new EasyPieChart(element, {
|
|
size: size,
|
|
barColor: barcolor,
|
|
trackColor: trackcolor,
|
|
scaleColor: scalecolor,
|
|
scaleLength: scalelength,
|
|
lineCap: linecap,
|
|
lineWidth: linewidth,
|
|
animate: {
|
|
duration: 1500,
|
|
enabled: true
|
|
},
|
|
easing: 'easeOutQuad', // Use our built-in easing function
|
|
onStep: function(from, to, percent) {
|
|
// Find the percentage element and update its text
|
|
const percentElement = element.querySelector('.js-percent');
|
|
if (percentElement) {
|
|
percentElement.textContent = Math.round(percent);
|
|
}
|
|
}
|
|
});
|
|
|
|
// Register the chart instance
|
|
window.easyPieChartRegistry.instances.push(chart);
|
|
window.easyPieChartRegistry.elements.push(element);
|
|
});
|
|
}); |