aztomiq 1.0.2 → 1.0.4
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/package.json +4 -1
- package/scripts/builds/assets.js +30 -22
- package/scripts/builds/data.js +30 -19
- package/server.js +125 -0
- package/src/assets/css/categories.css +90 -0
- package/src/assets/css/global.css +1815 -0
- package/src/assets/css/home.css +509 -0
- package/src/assets/css/master-layout.css +133 -0
- package/src/assets/css/sidebar-layout.css +264 -0
- package/src/assets/images/logo.svg +29 -0
- package/src/assets/js/global.js +373 -0
- package/src/assets/js/home.js +138 -0
- package/src/assets/js/user-mode.js +54 -0
- package/src/assets/vendor/qrcode/qrcode.min.js +1 -0
- package/src/locales/en/common.yaml +93 -0
- package/src/locales/vi/common.yaml +93 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aztomiq",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "AZtomiq - A comprehensive A-Z multi-tool framework with atomic architecture",
|
|
5
5
|
"main": "scripts/build.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,8 +9,11 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"bin",
|
|
11
11
|
"scripts",
|
|
12
|
+
"src/assets",
|
|
12
13
|
"src/includes",
|
|
13
14
|
"src/templates",
|
|
15
|
+
"src/locales",
|
|
16
|
+
"server.js",
|
|
14
17
|
"package.json",
|
|
15
18
|
"README.md",
|
|
16
19
|
"LICENSE"
|
package/scripts/builds/assets.js
CHANGED
|
@@ -82,38 +82,46 @@ async function buildAssets() {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
// 1. Global CSS
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
const cssDirs = [path.join(paths.CORE_ROOT, 'src/assets/css'), cssSrc].filter(fs.existsSync);
|
|
86
|
+
const cssFiles = {};
|
|
87
|
+
for (const dir of cssDirs) {
|
|
88
|
+
const files = fs.readdirSync(dir);
|
|
87
89
|
for (const file of files) {
|
|
88
|
-
if (
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
90
|
+
if (file.endsWith('.css')) cssFiles[file] = path.join(dir, file);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const [file, srcPath] of Object.entries(cssFiles)) {
|
|
95
|
+
const destPath = path.join(cssDist, file);
|
|
96
|
+
if (hasChanged(srcPath) || !fs.existsSync(destPath)) {
|
|
97
|
+
if (isSecure) {
|
|
98
|
+
console.time(`🎨 Minifying Global CSS: ${file}`);
|
|
99
|
+
try { execSync(`npx clean-css-cli -o "${destPath}" "${srcPath}"`); }
|
|
100
|
+
catch (e) { await fs.copy(srcPath, destPath); }
|
|
101
|
+
console.timeEnd(`🎨 Minifying Global CSS: ${file}`);
|
|
102
|
+
} else {
|
|
103
|
+
console.time(`🎨 Copying Global CSS: ${file}`);
|
|
104
|
+
await fs.copy(srcPath, destPath);
|
|
105
|
+
console.timeEnd(`🎨 Copying Global CSS: ${file}`);
|
|
102
106
|
}
|
|
103
107
|
}
|
|
104
108
|
}
|
|
105
109
|
|
|
106
110
|
// 2. Global JS
|
|
107
|
-
|
|
108
|
-
|
|
111
|
+
const jsDirs = [path.join(paths.CORE_ROOT, 'src/assets/js'), jsSrc].filter(fs.existsSync);
|
|
112
|
+
const jsFiles = {};
|
|
113
|
+
for (const dir of jsDirs) {
|
|
114
|
+
const files = fs.readdirSync(dir);
|
|
109
115
|
for (const file of files) {
|
|
110
|
-
if (
|
|
111
|
-
const srcPath = path.join(jsSrc, file);
|
|
112
|
-
const destPath = path.join(jsDist, file);
|
|
113
|
-
processJs(srcPath, destPath, file);
|
|
116
|
+
if (file.endsWith('.js')) jsFiles[file] = path.join(dir, file);
|
|
114
117
|
}
|
|
115
118
|
}
|
|
116
119
|
|
|
120
|
+
for (const [file, srcPath] of Object.entries(jsFiles)) {
|
|
121
|
+
const destPath = path.join(jsDist, file);
|
|
122
|
+
processJs(srcPath, destPath, file);
|
|
123
|
+
}
|
|
124
|
+
|
|
117
125
|
// 3. Feature Assets
|
|
118
126
|
if (await fs.pathExists(featuresDir)) {
|
|
119
127
|
const features = await fs.readdir(featuresDir);
|
package/scripts/builds/data.js
CHANGED
|
@@ -44,28 +44,39 @@ function loadLocales(lang) {
|
|
|
44
44
|
const srcDir = paths.SRC;
|
|
45
45
|
|
|
46
46
|
// 1. Load legacy file
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
const legacyPaths = [
|
|
48
|
+
path.join(paths.CORE_ROOT, 'src/locales', `${lang}.json`),
|
|
49
|
+
path.join(srcDir, 'locales', `${lang}.json`)
|
|
50
|
+
];
|
|
51
|
+
for (const p of legacyPaths) {
|
|
52
|
+
if (fs.existsSync(p)) {
|
|
53
|
+
try {
|
|
54
|
+
Object.assign(translations, typeof require(p) === 'string' ? JSON.parse(fs.readFileSync(p, 'utf8')) : require(p));
|
|
55
|
+
} catch (e) { console.error(`Error loading legacy locale ${lang} `, e); }
|
|
56
|
+
}
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
// 2. Load module folders
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
60
|
+
const dirPaths = [
|
|
61
|
+
path.join(paths.CORE_ROOT, 'src/locales', lang),
|
|
62
|
+
path.join(srcDir, 'locales', lang)
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
for (const dirPath of dirPaths) {
|
|
66
|
+
if (fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) {
|
|
67
|
+
const files = fs.readdirSync(dirPath);
|
|
68
|
+
for (const file of files) {
|
|
69
|
+
if (file.endsWith('.json')) {
|
|
70
|
+
try {
|
|
71
|
+
const content = JSON.parse(fs.readFileSync(path.join(dirPath, file), 'utf8'));
|
|
72
|
+
Object.assign(translations, content);
|
|
73
|
+
} catch (e) { console.error(`Error loading locale module ${lang}/${file}`, e); }
|
|
74
|
+
} else if (file.endsWith('.yaml') || file.endsWith('.yml')) {
|
|
75
|
+
try {
|
|
76
|
+
const content = yaml.load(fs.readFileSync(path.join(dirPath, file), 'utf8'));
|
|
77
|
+
Object.assign(translations, content);
|
|
78
|
+
} catch (e) { console.error(`Error loading locale module ${lang}/${file}`, e); }
|
|
79
|
+
}
|
|
69
80
|
}
|
|
70
81
|
}
|
|
71
82
|
}
|
package/server.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
|
|
2
|
+
const express = require('express');
|
|
3
|
+
const bodyParser = require('body-parser');
|
|
4
|
+
const cors = require('cors');
|
|
5
|
+
const fs = require('fs-extra');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const yaml = require('js-yaml');
|
|
8
|
+
const { exec } = require('child_process');
|
|
9
|
+
|
|
10
|
+
const app = express();
|
|
11
|
+
const PORT = 3000;
|
|
12
|
+
|
|
13
|
+
// Configuration
|
|
14
|
+
const PROJECT_ROOT = process.env.AZTOMIQ_PROJECT_ROOT || process.cwd();
|
|
15
|
+
const SRC_DIR = path.join(PROJECT_ROOT, 'src');
|
|
16
|
+
const FEATURES_DIR = path.join(SRC_DIR, 'features');
|
|
17
|
+
const DIST_DIR = path.join(PROJECT_ROOT, 'dist-dev');
|
|
18
|
+
|
|
19
|
+
app.use(cors());
|
|
20
|
+
app.use(bodyParser.json());
|
|
21
|
+
|
|
22
|
+
// 1. Static Files (Serve the site)
|
|
23
|
+
app.use(express.static(DIST_DIR));
|
|
24
|
+
|
|
25
|
+
// 2. Admin API
|
|
26
|
+
// GET /api/features - List all features and their configs
|
|
27
|
+
app.get('/api/features', (req, res) => {
|
|
28
|
+
try {
|
|
29
|
+
const features = fs.readdirSync(FEATURES_DIR).filter(f => {
|
|
30
|
+
return fs.statSync(path.join(FEATURES_DIR, f)).isDirectory();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const data = features.map(feature => {
|
|
34
|
+
const configPath = path.join(FEATURES_DIR, feature, 'tool.yaml');
|
|
35
|
+
if (fs.existsSync(configPath)) {
|
|
36
|
+
const config = yaml.load(fs.readFileSync(configPath, 'utf8'));
|
|
37
|
+
return { id: feature, config };
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}).filter(item => item !== null);
|
|
41
|
+
|
|
42
|
+
res.json(data);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
res.status(500).json({ error: err.message });
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// POST /api/features/:id - Update feature config
|
|
49
|
+
app.post('/api/features/:id', (req, res) => {
|
|
50
|
+
const { id } = req.params;
|
|
51
|
+
const newConfig = req.body;
|
|
52
|
+
const configPath = path.join(FEATURES_DIR, id, 'tool.yaml');
|
|
53
|
+
|
|
54
|
+
if (!fs.existsSync(configPath)) {
|
|
55
|
+
return res.status(404).json({ error: 'Feature not found' });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
fs.writeFileSync(configPath, yaml.dump(newConfig), 'utf8');
|
|
60
|
+
console.log(`📝 Updated config for ${id}`);
|
|
61
|
+
|
|
62
|
+
console.log('🔄 Triggering rebuild...');
|
|
63
|
+
exec('npx aztomiq build', { cwd: PROJECT_ROOT }, (error, stdout, stderr) => {
|
|
64
|
+
if (error) console.error(`Build error: ${error}`);
|
|
65
|
+
else console.log(`✅ Build complete`);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
res.json({ success: true, message: 'Config updated and rebuild triggered' });
|
|
69
|
+
} catch (err) {
|
|
70
|
+
res.status(500).json({ error: err.message });
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// GET /api/global
|
|
75
|
+
app.get('/api/global', (req, res) => {
|
|
76
|
+
try {
|
|
77
|
+
const globalPath = path.join(SRC_DIR, 'data', 'global.yaml');
|
|
78
|
+
if (fs.existsSync(globalPath)) {
|
|
79
|
+
const config = yaml.load(fs.readFileSync(globalPath, 'utf8'));
|
|
80
|
+
res.json(config);
|
|
81
|
+
} else {
|
|
82
|
+
res.json({});
|
|
83
|
+
}
|
|
84
|
+
} catch (err) {
|
|
85
|
+
res.status(500).json({ error: err.message });
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// POST /api/global
|
|
90
|
+
app.post('/api/global', (req, res) => {
|
|
91
|
+
try {
|
|
92
|
+
const globalPath = path.join(SRC_DIR, 'data', 'global.yaml');
|
|
93
|
+
const newConfig = req.body;
|
|
94
|
+
|
|
95
|
+
fs.writeFileSync(globalPath, yaml.dump(newConfig), 'utf8');
|
|
96
|
+
console.log(`📝 Updated Global Config`);
|
|
97
|
+
|
|
98
|
+
console.log('🔄 Triggering rebuild...');
|
|
99
|
+
exec('npx aztomiq build', { cwd: PROJECT_ROOT }, (error, stdout, stderr) => {
|
|
100
|
+
if (error) console.error(`Build error: ${error}`);
|
|
101
|
+
else console.log(`✅ Build complete`);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
res.json({ success: true, message: 'Global config updated' });
|
|
105
|
+
} catch (err) {
|
|
106
|
+
res.status(500).json({ error: err.message });
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Fallback to index.html for SPA routing (only for page requests, not assets)
|
|
111
|
+
app.use((req, res) => {
|
|
112
|
+
// If request has an extension, it's likely a missing asset
|
|
113
|
+
if (path.extname(req.path)) {
|
|
114
|
+
return res.status(404).send('Not found');
|
|
115
|
+
}
|
|
116
|
+
res.sendFile(path.join(DIST_DIR, 'index.html'));
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
app.listen(PORT, () => {
|
|
120
|
+
console.log(`
|
|
121
|
+
🚀 Admin Server running at http://localhost:${PORT}/admin
|
|
122
|
+
👉 Admin API: http://localhost:${PORT}/api/features
|
|
123
|
+
👉 Website: http://localhost:${PORT}/
|
|
124
|
+
`);
|
|
125
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/* Masonry Grid Layout */
|
|
2
|
+
.categories-grid {
|
|
3
|
+
column-count: 2;
|
|
4
|
+
column-gap: 2rem;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
@media (max-width: 900px) {
|
|
8
|
+
.categories-grid {
|
|
9
|
+
column-count: 1;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.category-card {
|
|
14
|
+
break-inside: avoid; /* Prevent card splitting */
|
|
15
|
+
margin-bottom: 2rem;
|
|
16
|
+
padding: 1.5rem;
|
|
17
|
+
background: var(--card-bg);
|
|
18
|
+
border: 1px solid var(--border-color);
|
|
19
|
+
border-radius: 12px;
|
|
20
|
+
box-shadow: var(--shadow);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.category-title {
|
|
24
|
+
font-size: 1.25rem;
|
|
25
|
+
margin-bottom: 1.5rem;
|
|
26
|
+
display: flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
gap: 0.75rem;
|
|
29
|
+
border-bottom: 1px solid var(--border-color);
|
|
30
|
+
padding-bottom: 1rem;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.cat-icon {
|
|
34
|
+
display: inline-flex;
|
|
35
|
+
align-items: center;
|
|
36
|
+
justify-content: center;
|
|
37
|
+
width: 36px;
|
|
38
|
+
height: 36px;
|
|
39
|
+
background: var(--bg-hover);
|
|
40
|
+
border-radius: 8px;
|
|
41
|
+
color: var(--primary-color);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.cat-count {
|
|
45
|
+
margin-left: auto;
|
|
46
|
+
font-size: 0.8rem;
|
|
47
|
+
background: var(--bg-hover);
|
|
48
|
+
padding: 0.2rem 0.6rem;
|
|
49
|
+
border-radius: 20px;
|
|
50
|
+
color: var(--text-muted);
|
|
51
|
+
white-space: nowrap;
|
|
52
|
+
display: inline-flex;
|
|
53
|
+
align-items: center;
|
|
54
|
+
border: 1px solid var(--border-color);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.cat-tools-list {
|
|
58
|
+
display: flex;
|
|
59
|
+
flex-direction: column;
|
|
60
|
+
gap: 0.5rem;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.cat-tool-item {
|
|
64
|
+
display: flex;
|
|
65
|
+
align-items: center;
|
|
66
|
+
gap: 0.75rem;
|
|
67
|
+
padding: 0.6rem 0.8rem;
|
|
68
|
+
border-radius: 8px;
|
|
69
|
+
color: var(--text-color); /* Updated text color */
|
|
70
|
+
transition: all 0.2s;
|
|
71
|
+
border: 1px solid transparent; /* Prevent layout shift on hover border */
|
|
72
|
+
position: relative;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.cat-tool-item:hover {
|
|
76
|
+
background: var(--bg-hover);
|
|
77
|
+
color: var(--primary-color);
|
|
78
|
+
transform: translateX(5px);
|
|
79
|
+
border-color: var(--border-color);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.tool-icon {
|
|
83
|
+
color: var(--text-muted);
|
|
84
|
+
display: flex;
|
|
85
|
+
align-items: center;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.cat-tool-item:hover .tool-icon {
|
|
89
|
+
color: var(--primary-color);
|
|
90
|
+
}
|