This is an automated email from the ASF dual-hosted git repository. ovilia pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/echarts-theme-builder.git
commit ccd9161dd0557422de3ddecc3c9c1562aaa96f64 Author: Ovilia <[email protected]> AuthorDate: Thu Sep 25 16:31:21 2025 +0800 chore: update for release --- en/body.html | 2 + package.json | 2 +- scripts/release.js | 62 +++++++++ src/App.vue | 3 +- src/components/ChartPreviewPanel.vue | 4 +- src/components/ThemePanel.vue | 5 +- src/entries/en.ts | 36 ++++++ src/entries/zh.ts | 36 ++++++ src/stores/theme.ts | 4 +- src/utils/chartConfigs.ts | 3 +- src/utils/themeGenerator.ts | 14 +-- vite.config.ts | 237 +++++++++++++++++------------------ zh/body.html | 2 + 13 files changed, 268 insertions(+), 142 deletions(-) diff --git a/en/body.html b/en/body.html new file mode 100644 index 0000000..1db2bb3 --- /dev/null +++ b/en/body.html @@ -0,0 +1,2 @@ +<div id="theme-builder"></div> +<script type="module" src="../src/entries/en.ts"></script> diff --git a/package.json b/package.json index f077d96..ffa6e74 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "build": "vue-tsc -b && vite build", "preview": "http-server app", "serve": "http-server app", - "release": "RELEASE=true vue-tsc -b && RELEASE=true vite build" + "release": "npm run build && node scripts/release.js" }, "dependencies": { "echarts": "^6.0.0", diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 0000000..83ad7c0 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,62 @@ +import fs from 'fs-extra'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import config from '../config/env.asf.js'; + +// Get __dirname equivalent in ESM +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const appDir = path.resolve(__dirname, '../app'); +const releaseDestDir = config.releaseDestDir; +const ecWWWGeneratedDir = config.ecWWWGeneratedDir; + +console.log('Releasing theme builder...'); + +// Make sure directories exist +fs.ensureDirSync(path.join(ecWWWGeneratedDir, 'en', 'theme-builder')); +fs.ensureDirSync(path.join(ecWWWGeneratedDir, 'zh', 'theme-builder')); +fs.ensureDirSync(path.join(releaseDestDir, 'en', 'theme-builder')); +fs.ensureDirSync(path.join(releaseDestDir, 'zh', 'theme-builder')); + +// Clean up existing JS and CSS files in the releaseDestDir +console.log('Cleaning up old CSS and JS files...'); +['en', 'zh'].forEach(lang => { + const themeBuilderDir = path.join(releaseDestDir, lang, 'theme-builder'); + if (fs.existsSync(themeBuilderDir)) { + const files = fs.readdirSync(themeBuilderDir); + files.forEach(file => { + if (file.endsWith('.js') || file.endsWith('.css')) { + const filePath = path.join(themeBuilderDir, file); + console.log(`Removing: ${filePath}`); + fs.removeSync(filePath); + } + }); + } +}); + +// Move body.html files +console.log('Moving body.html files to echarts-www...'); +fs.copySync( + path.join(appDir, 'en', 'body.html'), + path.join(ecWWWGeneratedDir, 'en', 'theme-builder', 'body.html') +); +fs.copySync( + path.join(appDir, 'zh', 'body.html'), + path.join(ecWWWGeneratedDir, 'zh', 'theme-builder', 'body.html') +); + +// Move theme-builder files to website +console.log('Moving theme-builder files to echarts-website...'); +fs.copySync( + path.join(appDir, 'en', 'theme-builder'), + path.join(releaseDestDir, 'en', 'theme-builder'), + { overwrite: true } +); +fs.copySync( + path.join(appDir, 'zh', 'theme-builder'), + path.join(releaseDestDir, 'zh', 'theme-builder'), + { overwrite: true } +); + +console.log('Release completed successfully!'); diff --git a/src/App.vue b/src/App.vue index 2e38523..3867abf 100644 --- a/src/App.vue +++ b/src/App.vue @@ -11,7 +11,8 @@ const { switchLanguage, currentLanguage, getAvailableLanguages } = useLocalizati const currentLang = ref(currentLanguage) const availableLocales = getAvailableLanguages() // Only show language selector in dev/preview mode -const showLanguageSelector = availableLocales.length > 0 +// Use import.meta.env.DEV to only show in development mode +const showLanguageSelector = import.meta.env.DEV && availableLocales.length > 0 const onLanguageChange = (lang: string) => { switchLanguage(lang) diff --git a/src/components/ChartPreviewPanel.vue b/src/components/ChartPreviewPanel.vue index 37e0919..e68123e 100644 --- a/src/components/ChartPreviewPanel.vue +++ b/src/components/ChartPreviewPanel.vue @@ -65,7 +65,7 @@ function setChartRef(el: any, index: number) { // Register and apply current theme function registerCurrentTheme() { - const currentTheme = themeStore.getEChartsTheme(false) + const currentTheme = themeStore.getEChartsTheme() echarts.registerTheme('customized', currentTheme) } @@ -110,7 +110,7 @@ function _updateChartsImpl() { } // Get current theme and register with unique ID to force refresh - const currentTheme = themeStore.getEChartsTheme(false) + const currentTheme = themeStore.getEChartsTheme() const themeId = `customized-${Date.now()}` echarts.registerTheme(themeId, currentTheme) diff --git a/src/components/ThemePanel.vue b/src/components/ThemePanel.vue index bd360df..2e226bc 100644 --- a/src/components/ThemePanel.vue +++ b/src/components/ThemePanel.vue @@ -670,7 +670,7 @@ const validateGridValue = (position: 'left' | 'right' | 'top' | 'bottom') => { // Methods const downloadTheme = async () => { try { - const themeConfig = themeStore.getEChartsTheme(true) + const themeConfig = themeStore.getEChartsTheme() const jsContent = themeStore.getThemeJsFile() const filename = themeName.value || 'customized' @@ -953,7 +953,8 @@ const handleFileImport = async (event: Event) => { flex-wrap: wrap; justify-content: space-between; width: auto; - height: 22px; + box-sizing: border-box; + height: 32px; margin-bottom: 5px; overflow: hidden; border: 1px solid #eee; diff --git a/src/entries/en.ts b/src/entries/en.ts new file mode 100644 index 0000000..ca3adfd --- /dev/null +++ b/src/entries/en.ts @@ -0,0 +1,36 @@ +// English Entry Point +import { createApp } from 'vue' +import { + Col, + Row, + Collapse, + CollapseItem, + Field, + Button, + Icon, + Checkbox, + RadioGroup, + Radio +} from 'vant' +import 'vant/lib/index.css' +import '../style.css' +import App from '../App.vue' +import i18n from '../i18n' +import { setLocale } from '../i18n' + +// Force English locale +setLocale('en') + +const app = createApp(App) +app.use(i18n) +app.use(Col) +app.use(Row) +app.use(Collapse) +app.use(CollapseItem) +app.use(Field) +app.use(Button) +app.use(Icon) +app.use(Checkbox) +app.use(RadioGroup) +app.use(Radio) +app.mount('#theme-builder') diff --git a/src/entries/zh.ts b/src/entries/zh.ts new file mode 100644 index 0000000..ae51117 --- /dev/null +++ b/src/entries/zh.ts @@ -0,0 +1,36 @@ +// Chinese Entry Point +import { createApp } from 'vue' +import { + Col, + Row, + Collapse, + CollapseItem, + Field, + Button, + Icon, + Checkbox, + RadioGroup, + Radio +} from 'vant' +import 'vant/lib/index.css' +import '../style.css' +import App from '../App.vue' +import i18n from '../i18n' +import { setLocale } from '../i18n' + +// Force Chinese locale +setLocale('zh') + +const app = createApp(App) +app.use(i18n) +app.use(Col) +app.use(Row) +app.use(Collapse) +app.use(CollapseItem) +app.use(Field) +app.use(Button) +app.use(Icon) +app.use(Checkbox) +app.use(RadioGroup) +app.use(Radio) +app.mount('#theme-builder') diff --git a/src/stores/theme.ts b/src/stores/theme.ts index 4545de9..97e1550 100644 --- a/src/stores/theme.ts +++ b/src/stores/theme.ts @@ -315,10 +315,10 @@ const createThemeStore = () => { return cleanedData } - const getEChartsTheme = (isToExport: boolean = false) => { + const getEChartsTheme = () => { // Convert reactive object to plain object to ensure proper data passing const plainTheme = JSON.parse(JSON.stringify(theme)) - return generateEChartsTheme(plainTheme, isToExport) + return generateEChartsTheme(plainTheme) } const getThemeJsFile = () => { diff --git a/src/utils/chartConfigs.ts b/src/utils/chartConfigs.ts index 643c43a..0360c93 100644 --- a/src/utils/chartConfigs.ts +++ b/src/utils/chartConfigs.ts @@ -430,7 +430,8 @@ export function getChartConfigs(seriesCnt: number = 4): ChartConfig[] { layout: 'force', roam: true, label: { - show: true + show: true, + color: 'auto' }, force: { repulsion: 400, diff --git a/src/utils/themeGenerator.ts b/src/utils/themeGenerator.ts index 6ef822d..8b162c5 100644 --- a/src/utils/themeGenerator.ts +++ b/src/utils/themeGenerator.ts @@ -3,13 +3,9 @@ import type { ThemeData } from '../types/theme' /** * Generate ECharts theme configuration based on theme data * @param themeData - Theme configuration data - * @param isToExport - Reserved for backward compatibility (no longer used) * @returns ECharts theme configuration object */ -export function generateEChartsTheme(themeData: ThemeData, isToExport: boolean = false) { - // Halloween pumpkin symbol path - const pumpkin = 'path://M237.062,81.761L237.062,81.761c-12.144-14.24-25.701-20.1-40.68-19.072 c-10.843,0.747-20.938,5.154-30.257,13.127c-9.51-5.843-19.8-9.227-30.859-10.366c0.521-3.197,1.46-6.306,2.85-9.363 c3.458-7.038,8.907-12.741,16.331-17.296c-5.609-3.384-11.227-6.799-16.854-10.279c-16.257,8.104-25.06,20.601-26.463,38.417 c-7.599,1.705-14.685,4.486-21.247,8.437c-9.164-7.677-18.996-11.917-29.496-12.632c-14.819-0.998-28.467,4.787-40.938,18.827 C6.445,96.182,0,114.867,0,136.242c-0.007 [...] - +export function generateEChartsTheme(themeData: ThemeData) { // Generate series style configuration const seriesStyle = { itemStyle: { @@ -33,12 +29,6 @@ export function generateEChartsTheme(themeData: ThemeData, isToExport: boolean = itemStyle: itemStyle } as any - // Special case for Halloween theme - if (themeData.symbol === 'halloween') { - seriesStyle.symbol = pumpkin - border.symbol = pumpkin - } - // Generate map configuration const mapConfig = { itemStyle: { @@ -271,7 +261,7 @@ export function generateEChartsTheme(themeData: ThemeData, isToExport: boolean = * @returns JavaScript file content as string */ export function generateThemeJsFile(themeData: ThemeData, themeName: string): string { - const themeConfig = generateEChartsTheme(themeData, true) + const themeConfig = generateEChartsTheme(themeData) // Format theme with 4 spaces indentation let themeJson = JSON.stringify(themeConfig, null, ' ') diff --git a/vite.config.ts b/vite.config.ts index 7159e53..23acac6 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,150 +1,145 @@ -import { defineConfig } from 'vite' +import { defineConfig, type Plugin } from 'vite' import vue from '@vitejs/plugin-vue' import path from 'path' import fs from 'fs' import fse from 'fs-extra' -/** - * Load release config from env.asf.js - */ -function loadReleaseConfig() { - const configModule = require('./config/env.asf.js') - return configModule.default || configModule -} - -// Check if release mode is enabled with RELEASE=true environment variable -const isRelease = process.env.RELEASE === 'true' - -// https://vite.dev/config/ -export default defineConfig({ - define: { - // 设置环境变量,在前端代码中可以通过import.meta.env访问 - 'import.meta.env.VITE_MODE': JSON.stringify(isRelease ? 'release' : 'development') - }, - plugins: [ - vue(), - { - name: 'theme-builder-processor', - closeBundle: async () => { - // Handle HTML files - Create language-specific index.html files - if (fs.existsSync('app/index.html')) { - console.log('Processing HTML output...') - - // Create directories if they don't exist - fs.mkdirSync('app/en', { recursive: true }) - fs.mkdirSync('app/zh', { recursive: true }) - fs.mkdirSync('app/en/theme-builder', { recursive: true }) - fs.mkdirSync('app/zh/theme-builder', { recursive: true }) - - // Generate HTML content - just div + script tag - const divContent = '<div id="theme-builder"></div>' - const scriptTag = '<script type="module" src="./theme-builder/app.min.js"></script>' - - // Create simplified index.html for both languages - const indexHTML = `${divContent}\n${scriptTag}` - - // Write index.html files (only these, no body.html) - fs.writeFileSync('app/en/index.html', indexHTML, 'utf-8') - fs.writeFileSync('app/zh/index.html', indexHTML, 'utf-8') - - // Remove the original index.html - fs.unlinkSync('app/index.html') +const SUPPORTED_LANGUAGES: string[] = ['en', 'zh']; +const OUTPUT_DIR: string = 'app'; + +// Custom plugin to handle HTML output for different languages +function createHtmlOutputPlugin(): Plugin { + return { + name: 'html-output-plugin', + enforce: 'post', + apply: 'build', + closeBundle: async () => { + const distDir = path.resolve(__dirname, 'dist'); + const publicDir = path.resolve(__dirname, 'public'); + + // Create language directories if they don't exist + for (const lang of SUPPORTED_LANGUAGES) { + const langDir = path.join(OUTPUT_DIR, lang); + const assetDir = path.join(langDir, 'theme-builder'); + const themesDir = path.join(assetDir, 'themes'); + + // Ensure directories exist and clean any existing assets + await fse.ensureDir(langDir); + await fse.ensureDir(assetDir); + + // Clean up existing JS and CSS files before copying new ones + const existingFiles = await fse.readdir(assetDir); + for (const file of existingFiles) { + if (file.endsWith('.js') || file.endsWith('.css')) { + await fse.remove(path.join(assetDir, file)); + } } - // Move CSS to shared styles directory - console.log('Processing CSS and shared resources...') - fs.mkdirSync('app/styles', { recursive: true }) + await fse.ensureDir(themesDir); - if (fs.existsSync('app/styles/main.css')) { - // CSS is already in the correct location from the build output - console.log('CSS output already in correct location') - } + // Find JS and CSS files in the dist directory + const files = await fse.readdir(distDir); + const jsFiles = files.filter(file => file.endsWith('.js')); + const cssFiles = files.filter(file => file.endsWith('.css')); - // Copy theme files from public to app/themes (common resource) - const themesDir = 'public/themes' - const themesDestination = 'app/themes' + // Read the HTML file to extract scripts and links to include in body.html + if (fs.existsSync(path.join(distDir, 'index.html'))) { + const htmlContent = await fse.readFile(path.join(distDir, 'index.html'), 'utf-8'); - if (fs.existsSync(themesDir)) { - fs.mkdirSync(themesDestination, { recursive: true }) + // Extract only the content within the body tag + let bodyContent = htmlContent.match(/<body[^>]*>([\s\S]*)<\/body>/i)?.[1] || ''; - // Copy theme JSON files - fs.readdirSync(themesDir).forEach((file) => { - if (file.endsWith('.json')) { - fs.copyFileSync(`${themesDir}/${file}`, `${themesDestination}/${file}`) - } - }) - } + // Get all script and link tags from head + const headScripts = (htmlContent.match(/<script[^>]*src="\.\/([^"]+)"[^>]*><\/script>/g) || []) + .map(script => script.replace(/src="\.\//g, 'src="theme-builder/')); - // Handle release mode - copy files from app to ecWWWGeneratedDir - if (isRelease) { - console.log('Starting release process...') - const config = loadReleaseConfig() + const headLinks = (htmlContent.match(/<link[^>]*href="\.\/([^"]+)"[^>]*>/g) || []) + .map(link => link.replace(/href="\.\//g, 'href="theme-builder/')); - // Validate target directories - if (!config.ecWWWGeneratedDir) { - console.error('Error: ecWWWGeneratedDir not defined in config') - return - } + // Create a complete body.html with necessary script and link tags + let finalContent = [...headLinks, ...headScripts, bodyContent.trim()].join('\n'); - const ecWWWBaseDir = config.ecWWWGeneratedDir.replace('_generated', '') - if (!fs.existsSync(ecWWWBaseDir)) { - console.error(`Error: ECharts www project not found: ${ecWWWBaseDir}`) - return - } + // Replace any references to themes in the content + finalContent = finalContent.replace(/(['"])\.?\/themes\//g, '$1./theme-builder/themes/'); - // Create destination directory if needed - fse.ensureDirSync(config.ecWWWGeneratedDir) + // Write the content to body.html + await fse.writeFile(path.join(langDir, 'body.html'), finalContent); + } - // Copy the entire app directory to ecWWWGeneratedDir - console.log(`Copying app contents to ${config.ecWWWGeneratedDir}`) + // Copy JS and CSS files to the theme-builder directory + for (const jsFile of jsFiles) { + await fse.copy( + path.join(distDir, jsFile), + path.join(assetDir, jsFile) + ); + } - // Copy app directory to ecWWWGeneratedDir - fse.copySync('app', config.ecWWWGeneratedDir) + for (const cssFile of cssFiles) { + await fse.copy( + path.join(distDir, cssFile), + path.join(assetDir, cssFile) + ); + } - console.log('Release process completed successfully!') + // Copy themes from public directory + if (fs.existsSync(path.join(publicDir, 'themes'))) { + await fse.copy( + path.join(publicDir, 'themes'), + themesDir + ); } } + + // Clean up the original dist directory + await fse.remove(distDir); + } + }; +} + +// Custom plugin to rewrite theme paths in JS files +function createThemePathRewritePlugin(): Plugin { + return { + name: 'theme-path-rewrite', + transform(code, id) { + if (id.endsWith('.js') || id.endsWith('.ts')) { + // Replace theme path references + return code.replace(/(['"`])\.?\/themes\//g, '$1./theme-builder/themes/'); + } + return code; } + }; +} + +export default defineConfig({ + plugins: [ + vue({ + template: { + transformAssetUrls: { + base: '/src', + includeAbsolute: false, + }, + } + }), + createThemePathRewritePlugin(), + createHtmlOutputPlugin() ], build: { - outDir: 'app', + outDir: 'dist', // Temporary build directory emptyOutDir: true, + assetsDir: '.', // This will ensure assets are placed at the root level of the output directory rollupOptions: { - input: { - 'en': path.resolve(process.cwd(), 'index.html'), - 'zh': path.resolve(process.cwd(), 'index.html') - }, output: { - entryFileNames: () => { - // 统一放在各自语言目录下的theme-builder目录中 - return `[name]/theme-builder/app.min.js` - }, - chunkFileNames: (chunkInfo: any) => { - const name = chunkInfo.name || '' - // 根据名称推断语言 - if (name.startsWith('en') || name.includes('en-')) { - return `en/theme-builder/chunks/[name]-[hash].js` - } else { - return `zh/theme-builder/chunks/[name]-[hash].js` - } - }, - assetFileNames: (assetInfo: any) => { - const info = assetInfo.name || '' - - // For CSS files, put them in shared styles directory - if (info.endsWith('.css')) { - return `styles/main.css` - } - - // Common assets go to app root, others to language-specific directories - if (info.includes('assets/') || info.includes('images/')) { - return `assets/[name]-[hash][extname]` - } - - return `en/theme-builder/assets/[name]-[hash][extname]` - }, - manualChunks: undefined + // Customize output file names + entryFileNames: '[name]-[hash].js', + chunkFileNames: '[name]-[hash].js', + assetFileNames: '[name]-[hash].[ext]' } } + }, + base: './', // Make sure assets use relative paths + resolve: { + alias: { + '@': path.resolve(__dirname, './src') + } } -}) +}); diff --git a/zh/body.html b/zh/body.html new file mode 100644 index 0000000..42fe349 --- /dev/null +++ b/zh/body.html @@ -0,0 +1,2 @@ +<div id="theme-builder"></div> +<script type="module" src="../src/entries/zh.ts"></script> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
