@keenmate/pure-admin-core 2.3.6 → 2.5.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/README.md +23 -29
- package/dist/css/main.css +68 -148
- package/package.json +1 -5
- package/snippets/AUDIT.md +94 -0
- package/snippets/alerts.html +264 -89
- package/snippets/badges.html +193 -61
- package/snippets/buttons.html +178 -0
- package/snippets/callouts.html +210 -129
- package/snippets/cards.html +383 -200
- package/snippets/checkbox-lists.html +199 -65
- package/snippets/code.html +55 -11
- package/snippets/command-palette.html +401 -111
- package/snippets/comparison.html +144 -93
- package/snippets/customization.html +311 -104
- package/snippets/data-display.html +584 -0
- package/snippets/detail-panel.html +470 -138
- package/snippets/filter-card.html +246 -0
- package/snippets/forms.html +408 -308
- package/snippets/grid.html +253 -141
- package/snippets/layout.html +379 -480
- package/snippets/lists.html +144 -47
- package/snippets/loaders.html +64 -39
- package/snippets/manifest.json +330 -280
- package/snippets/modal-dialogs.html +137 -64
- package/snippets/modals.html +221 -151
- package/snippets/notifications.html +285 -0
- package/snippets/popconfirm.html +213 -19
- package/snippets/profile.html +290 -330
- package/snippets/statistics.html +247 -0
- package/snippets/tables.html +359 -150
- package/snippets/tabs.html +129 -45
- package/snippets/timeline.html +123 -56
- package/snippets/toasts.html +179 -31
- package/snippets/tooltips.html +199 -81
- package/snippets/typography.html +183 -58
- package/snippets/utilities.html +511 -415
- package/snippets/virtual-scroll.html +201 -75
- package/snippets/web-daterangepicker.html +369 -189
- package/snippets/web-multiselect.html +360 -124
- package/src/scss/core-components/_alerts.scss +51 -12
- package/src/scss/core-components/_pagers.scss +1 -1
- package/src/scss/core-components/_popconfirm.scss +35 -13
- package/src/scss/core-components/_profile.scss +18 -8
- package/src/scss/core-components/_statistics.scss +12 -12
- package/src/scss/core-components/_tables.scss +2 -134
- package/src/scss/variables/_components.scss +17 -2
- package/scripts/download-themes.js +0 -351
|
@@ -1,351 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Download themes from pureadmin.io
|
|
5
|
-
*
|
|
6
|
-
* Reads themes.json + .themes.json (local override) and downloads
|
|
7
|
-
* themes that don't have a local "path" set.
|
|
8
|
-
*
|
|
9
|
-
* Format:
|
|
10
|
-
* {
|
|
11
|
-
* "themes": {
|
|
12
|
-
* "audi": {}, // download from pureadmin.io
|
|
13
|
-
* "corporate": { "path": "../my-themes/corp" }, // use local path
|
|
14
|
-
* "custom": { "url": "https://..." } // download from custom URL
|
|
15
|
-
* }
|
|
16
|
-
* }
|
|
17
|
-
*
|
|
18
|
-
* Downloaded themes are saved to ./themes/{name}/dist/{name}.css
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
const fs = require('fs');
|
|
22
|
-
const path = require('path');
|
|
23
|
-
const https = require('https');
|
|
24
|
-
const http = require('http');
|
|
25
|
-
|
|
26
|
-
const BUNDLE_URL = 'https://pureadmin.io/api/bundle';
|
|
27
|
-
const PROJECT_ROOT = process.cwd();
|
|
28
|
-
const THEMES_DIR = path.join(PROJECT_ROOT, 'themes');
|
|
29
|
-
const THEMES_CONFIG = path.join(PROJECT_ROOT, 'themes.json');
|
|
30
|
-
const THEMES_LOCAL = path.join(PROJECT_ROOT, '.themes.json');
|
|
31
|
-
|
|
32
|
-
function download(url) {
|
|
33
|
-
return new Promise((resolve, reject) => {
|
|
34
|
-
const client = url.startsWith('https') ? https : http;
|
|
35
|
-
|
|
36
|
-
client.get(url, (res) => {
|
|
37
|
-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
38
|
-
return download(res.headers.location).then(resolve).catch(reject);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (res.statusCode !== 200) {
|
|
42
|
-
reject(new Error(`HTTP ${res.statusCode} for ${url}`));
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const chunks = [];
|
|
47
|
-
res.on('data', chunk => chunks.push(chunk));
|
|
48
|
-
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
49
|
-
res.on('error', reject);
|
|
50
|
-
}).on('error', reject);
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function extractZip(buffer, destDir) {
|
|
55
|
-
const tmpFile = path.join(destDir, '..', '_tmp_theme.zip');
|
|
56
|
-
if (!fs.existsSync(path.dirname(tmpFile))) {
|
|
57
|
-
fs.mkdirSync(path.dirname(tmpFile), { recursive: true });
|
|
58
|
-
}
|
|
59
|
-
fs.writeFileSync(tmpFile, buffer);
|
|
60
|
-
|
|
61
|
-
const { execSync } = require('child_process');
|
|
62
|
-
try {
|
|
63
|
-
execSync(`unzip -o "${tmpFile}" -d "${destDir}"`, { stdio: 'ignore' });
|
|
64
|
-
} catch (e) {
|
|
65
|
-
try {
|
|
66
|
-
execSync(`powershell -Command "Expand-Archive -Force -Path '${tmpFile}' -DestinationPath '${destDir}'"`, { stdio: 'ignore' });
|
|
67
|
-
} catch (e2) {
|
|
68
|
-
fs.unlinkSync(tmpFile);
|
|
69
|
-
throw new Error('Could not extract zip. Install unzip or use PowerShell.');
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
fs.unlinkSync(tmpFile);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function findThemeCss(dir, themeName) {
|
|
76
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
77
|
-
|
|
78
|
-
for (const entry of entries) {
|
|
79
|
-
const fullPath = path.join(dir, entry.name);
|
|
80
|
-
|
|
81
|
-
if (entry.isFile() && entry.name === `${themeName}.css`) {
|
|
82
|
-
return fullPath;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (entry.isDirectory()) {
|
|
86
|
-
const found = findThemeCss(fullPath, themeName);
|
|
87
|
-
if (found) return found;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return null;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function getThemeVersion(themeDir) {
|
|
95
|
-
const manifestPath = path.join(themeDir, 'theme.json');
|
|
96
|
-
if (fs.existsSync(manifestPath)) {
|
|
97
|
-
try {
|
|
98
|
-
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
99
|
-
return manifest.version || null;
|
|
100
|
-
} catch (e) {
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function copyDir(src, dest) {
|
|
108
|
-
if (!fs.existsSync(dest)) {
|
|
109
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
113
|
-
for (const entry of entries) {
|
|
114
|
-
const srcPath = path.join(src, entry.name);
|
|
115
|
-
const destPath = path.join(dest, entry.name);
|
|
116
|
-
|
|
117
|
-
if (entry.isDirectory()) {
|
|
118
|
-
copyDir(srcPath, destPath);
|
|
119
|
-
} else {
|
|
120
|
-
fs.copyFileSync(srcPath, destPath);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
async function main() {
|
|
126
|
-
// Load themes.json (base) + .themes.json (local overrides)
|
|
127
|
-
let themes = {};
|
|
128
|
-
|
|
129
|
-
if (fs.existsSync(THEMES_CONFIG)) {
|
|
130
|
-
const base = JSON.parse(fs.readFileSync(THEMES_CONFIG, 'utf-8'));
|
|
131
|
-
themes = { ...themes, ...(base.themes || {}) };
|
|
132
|
-
console.log(`Loaded themes.json (${Object.keys(base.themes || {}).length} theme(s))`);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (fs.existsSync(THEMES_LOCAL)) {
|
|
136
|
-
const local = JSON.parse(fs.readFileSync(THEMES_LOCAL, 'utf-8'));
|
|
137
|
-
themes = { ...themes, ...(local.themes || {}) };
|
|
138
|
-
console.log(`Loaded .themes.json (${Object.keys(local.themes || {}).length} override(s))`);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (Object.keys(themes).length === 0) {
|
|
142
|
-
console.error('No themes found. Create themes.json or .themes.json, e.g.:');
|
|
143
|
-
console.error(' { "themes": { "audi": {}, "corporate": {} } }');
|
|
144
|
-
process.exit(1);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
console.log('');
|
|
148
|
-
|
|
149
|
-
// Ensure themes directory exists
|
|
150
|
-
if (!fs.existsSync(THEMES_DIR)) {
|
|
151
|
-
fs.mkdirSync(THEMES_DIR, { recursive: true });
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Separate themes by type
|
|
155
|
-
const bundleThemes = []; // No path, no url — download from bundle
|
|
156
|
-
const urlThemes = []; // Custom url
|
|
157
|
-
const localThemes = []; // Has path — skip
|
|
158
|
-
|
|
159
|
-
for (const [name, config] of Object.entries(themes)) {
|
|
160
|
-
const cfg = config || {};
|
|
161
|
-
if (cfg.path) {
|
|
162
|
-
localThemes.push(name);
|
|
163
|
-
} else if (cfg.url) {
|
|
164
|
-
urlThemes.push({ name, url: cfg.url });
|
|
165
|
-
} else {
|
|
166
|
-
bundleThemes.push(name);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
let downloaded = 0;
|
|
171
|
-
|
|
172
|
-
// Download named themes via bundle (single request)
|
|
173
|
-
if (bundleThemes.length > 0) {
|
|
174
|
-
process.stdout.write(`Fetching theme bundle from pureadmin.io (${bundleThemes.length} theme(s))...`);
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
const data = await download(BUNDLE_URL);
|
|
178
|
-
console.log(` ${(data.length / 1024).toFixed(0)}KB`);
|
|
179
|
-
|
|
180
|
-
// Extract bundle to temp dir
|
|
181
|
-
const tmpDir = path.join(THEMES_DIR, '_bundle_tmp');
|
|
182
|
-
if (fs.existsSync(tmpDir)) {
|
|
183
|
-
fs.rmSync(tmpDir, { recursive: true });
|
|
184
|
-
}
|
|
185
|
-
fs.mkdirSync(tmpDir, { recursive: true });
|
|
186
|
-
extractZip(data, tmpDir);
|
|
187
|
-
|
|
188
|
-
// Find and copy requested themes
|
|
189
|
-
for (const name of bundleThemes) {
|
|
190
|
-
const cssFile = findThemeCss(tmpDir, name);
|
|
191
|
-
|
|
192
|
-
if (cssFile) {
|
|
193
|
-
const themeDestDir = path.join(THEMES_DIR, name);
|
|
194
|
-
const distDir = path.join(themeDestDir, 'dist');
|
|
195
|
-
if (!fs.existsSync(distDir)) {
|
|
196
|
-
fs.mkdirSync(distDir, { recursive: true });
|
|
197
|
-
}
|
|
198
|
-
fs.copyFileSync(cssFile, path.join(distDir, `${name}.css`));
|
|
199
|
-
|
|
200
|
-
// Copy assets dir if it exists (fonts etc.)
|
|
201
|
-
const assetsDir = path.join(path.dirname(cssFile), 'assets');
|
|
202
|
-
if (fs.existsSync(assetsDir)) {
|
|
203
|
-
copyDir(assetsDir, path.join(distDir, 'assets'));
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Copy theme.json manifest if it exists
|
|
207
|
-
const themeJsonSrc = path.join(path.dirname(cssFile), '..', 'theme.json');
|
|
208
|
-
if (fs.existsSync(themeJsonSrc)) {
|
|
209
|
-
fs.copyFileSync(themeJsonSrc, path.join(themeDestDir, 'theme.json'));
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Read version from theme.json
|
|
213
|
-
const version = getThemeVersion(themeDestDir);
|
|
214
|
-
console.log(` ${name}: extracted${version ? ` (v${version})` : ''}`);
|
|
215
|
-
downloaded++;
|
|
216
|
-
} else {
|
|
217
|
-
console.log(` ${name}: NOT FOUND in bundle`);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Clean up temp dir
|
|
222
|
-
fs.rmSync(tmpDir, { recursive: true });
|
|
223
|
-
} catch (err) {
|
|
224
|
-
console.log(` FAILED: ${err.message}`);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Download URL themes individually
|
|
229
|
-
for (const { name, url } of urlThemes) {
|
|
230
|
-
process.stdout.write(` ${name}: downloading from ${url}...`);
|
|
231
|
-
|
|
232
|
-
try {
|
|
233
|
-
const data = await download(url);
|
|
234
|
-
const themeDestDir = path.join(THEMES_DIR, name);
|
|
235
|
-
|
|
236
|
-
if (data[0] === 0x50 && data[1] === 0x4B) {
|
|
237
|
-
if (!fs.existsSync(themeDestDir)) {
|
|
238
|
-
fs.mkdirSync(themeDestDir, { recursive: true });
|
|
239
|
-
}
|
|
240
|
-
extractZip(data, themeDestDir);
|
|
241
|
-
} else {
|
|
242
|
-
const distDir = path.join(themeDestDir, 'dist');
|
|
243
|
-
if (!fs.existsSync(distDir)) {
|
|
244
|
-
fs.mkdirSync(distDir, { recursive: true });
|
|
245
|
-
}
|
|
246
|
-
fs.writeFileSync(path.join(distDir, `${name}.css`), data);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
console.log(' done');
|
|
250
|
-
downloaded++;
|
|
251
|
-
} catch (err) {
|
|
252
|
-
console.log(` FAILED: ${err.message}`);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// Report skipped
|
|
257
|
-
for (const name of localThemes) {
|
|
258
|
-
console.log(` ${name}: local path (${themes[name].path}), skipping`);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
console.log(`\nDownloaded ${downloaded} theme(s), skipped ${localThemes.length} local path(s)`);
|
|
262
|
-
|
|
263
|
-
// Check compatibility with installed core version
|
|
264
|
-
checkCoreCompatibility();
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
/**
|
|
268
|
-
* Check if downloaded themes are compatible with the installed core version
|
|
269
|
-
*/
|
|
270
|
-
function checkCoreCompatibility() {
|
|
271
|
-
// Find installed core version
|
|
272
|
-
const corePkgPaths = [
|
|
273
|
-
path.join(PROJECT_ROOT, 'node_modules', '@keenmate', 'pure-admin-core', 'package.json'),
|
|
274
|
-
path.join(PROJECT_ROOT, '..', 'packages', 'core', 'package.json') // workspace
|
|
275
|
-
];
|
|
276
|
-
|
|
277
|
-
let coreVersion = null;
|
|
278
|
-
for (const p of corePkgPaths) {
|
|
279
|
-
if (fs.existsSync(p)) {
|
|
280
|
-
try {
|
|
281
|
-
const pkg = JSON.parse(fs.readFileSync(p, 'utf-8'));
|
|
282
|
-
coreVersion = pkg.version;
|
|
283
|
-
break;
|
|
284
|
-
} catch (e) {}
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (!coreVersion) return;
|
|
289
|
-
|
|
290
|
-
// Check each downloaded theme
|
|
291
|
-
const warnings = [];
|
|
292
|
-
const themeDirs = fs.readdirSync(THEMES_DIR, { withFileTypes: true })
|
|
293
|
-
.filter(d => d.isDirectory())
|
|
294
|
-
.map(d => d.name);
|
|
295
|
-
|
|
296
|
-
for (const name of themeDirs) {
|
|
297
|
-
const manifestPath = path.join(THEMES_DIR, name, 'theme.json');
|
|
298
|
-
if (!fs.existsSync(manifestPath)) continue;
|
|
299
|
-
|
|
300
|
-
try {
|
|
301
|
-
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
302
|
-
const requiredCore = manifest.dependencies && manifest.dependencies.core;
|
|
303
|
-
if (!requiredCore) continue;
|
|
304
|
-
|
|
305
|
-
if (!satisfiesSemver(coreVersion, requiredCore)) {
|
|
306
|
-
warnings.push(` ${name} (v${manifest.version}) requires core ${requiredCore}, installed: ${coreVersion}`);
|
|
307
|
-
}
|
|
308
|
-
} catch (e) {}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
if (warnings.length > 0) {
|
|
312
|
-
console.log('\n⚠ Compatibility warnings:');
|
|
313
|
-
warnings.forEach(w => console.log(w));
|
|
314
|
-
console.log('\nConsider updating @keenmate/pure-admin-core');
|
|
315
|
-
} else if (themeDirs.length > 0) {
|
|
316
|
-
console.log(`\nAll themes compatible with core v${coreVersion}`);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Simple semver satisfaction check for ^major.minor.patch ranges
|
|
322
|
-
*/
|
|
323
|
-
function satisfiesSemver(version, range) {
|
|
324
|
-
// Parse version
|
|
325
|
-
const v = version.replace(/^v/, '').split('.').map(Number);
|
|
326
|
-
|
|
327
|
-
// Parse range (supports ^x.y.z and x.y.z)
|
|
328
|
-
const caret = range.startsWith('^');
|
|
329
|
-
const r = range.replace(/^[\^~]/, '').split('.').map(Number);
|
|
330
|
-
|
|
331
|
-
if (caret) {
|
|
332
|
-
// ^2.0.0 means >=2.0.0 <3.0.0
|
|
333
|
-
// ^0.2.0 means >=0.2.0 <0.3.0
|
|
334
|
-
if (r[0] > 0) {
|
|
335
|
-
return v[0] === r[0] && (v[1] > r[1] || (v[1] === r[1] && v[2] >= r[2]));
|
|
336
|
-
} else if (r[1] > 0) {
|
|
337
|
-
return v[0] === 0 && v[1] === r[1] && v[2] >= r[2];
|
|
338
|
-
} else {
|
|
339
|
-
return v[0] === 0 && v[1] === 0 && v[2] === r[2];
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Exact match
|
|
344
|
-
return v[0] === r[0] && v[1] === r[1] && v[2] === r[2];
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
console.log('Downloading themes...\n');
|
|
348
|
-
main().catch(err => {
|
|
349
|
-
console.error('Fatal error:', err.message);
|
|
350
|
-
process.exit(1);
|
|
351
|
-
});
|