create-gardener 2.1.3 → 2.1.5
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 +108 -302
- package/package.json +1 -1
- package/template/.env +3 -0
- package/template/README.md +181 -0
- package/template/buildHelper.js +5 -0
- package/template/src/backend/controllers/gardener/hotReload.ts +34 -0
- package/template/src/backend/controllers/gardener/imageOptimiser.ts +114 -25
- package/template/src/backend/controllers/gardener/index.ts +1 -0
- package/template/src/backend/routes/gardener.route.ts +2 -1
- package/template/src/backend/server.ts +19 -1
- package/template/src/frontend/assets/remote/betterway.jpg +0 -0
- package/template/src/frontend/static/components/gardener/hotReloadbtn.js +25 -78
- package/template/src/frontend/static/gardenerDev.js +2 -18
- package/template/src/frontend/static/style.css +0 -77
- package/template/src/frontend/views/_.ejs +101 -89
- package/template/build/backend/controllers/gardener/addComponent.d.ts +0 -8
- package/template/build/backend/controllers/gardener/addComponent.d.ts.map +0 -1
- package/template/build/backend/controllers/gardener/addComponent.js +0 -19
- package/template/build/backend/controllers/gardener/addComponent.js.map +0 -1
- package/template/build/backend/controllers/gardener/addPage.d.ts +0 -3
- package/template/build/backend/controllers/gardener/addPage.d.ts.map +0 -1
- package/template/build/backend/controllers/gardener/addPage.js +0 -86
- package/template/build/backend/controllers/gardener/addPage.js.map +0 -1
- package/template/build/backend/controllers/gardener/createStatic.d.ts +0 -3
- package/template/build/backend/controllers/gardener/createStatic.d.ts.map +0 -1
- package/template/build/backend/controllers/gardener/createStatic.js +0 -63
- package/template/build/backend/controllers/gardener/createStatic.js.map +0 -1
- package/template/build/backend/controllers/gardener/imageOptimiser.d.ts +0 -3
- package/template/build/backend/controllers/gardener/imageOptimiser.d.ts.map +0 -1
- package/template/build/backend/controllers/gardener/imageOptimiser.js +0 -54
- package/template/build/backend/controllers/gardener/imageOptimiser.js.map +0 -1
- package/template/build/backend/controllers/gardener/index.d.ts +0 -6
- package/template/build/backend/controllers/gardener/index.d.ts.map +0 -1
- package/template/build/backend/controllers/gardener/index.js +0 -6
- package/template/build/backend/controllers/gardener/index.js.map +0 -1
- package/template/build/backend/controllers/gardener/saveTemplate.d.ts +0 -3
- package/template/build/backend/controllers/gardener/saveTemplate.d.ts.map +0 -1
- package/template/build/backend/controllers/gardener/saveTemplate.js +0 -36
- package/template/build/backend/controllers/gardener/saveTemplate.js.map +0 -1
- package/template/build/backend/libs/generateWebp.d.ts +0 -2
- package/template/build/backend/libs/generateWebp.d.ts.map +0 -1
- package/template/build/backend/libs/generateWebp.js +0 -16
- package/template/build/backend/libs/generateWebp.js.map +0 -1
- package/template/build/backend/routes/gardener.route.d.ts +0 -4
- package/template/build/backend/routes/gardener.route.d.ts.map +0 -1
- package/template/build/backend/routes/gardener.route.js +0 -18
- package/template/build/backend/routes/gardener.route.js.map +0 -1
- package/template/build/backend/server.d.ts +0 -2
- package/template/build/backend/server.d.ts.map +0 -1
- package/template/build/backend/server.js +0 -20
- package/template/build/backend/server.js.map +0 -1
- package/template/build/frontend/assets/favicon.png +0 -0
- package/template/build/frontend/assets/gardener.jpg +0 -0
- package/template/build/frontend/static/bundle/bundle._.js +0 -1
- package/template/build/frontend/static/bundle/bundle._about.js +0 -1
- package/template/build/frontend/static/bundle/bundle._kartik.js +0 -1
- package/template/build/frontend/static/bundle/bundle._new.js +0 -1
- package/template/build/frontend/static/bundle/bundle._ritish.js +0 -1
- package/template/build/frontend/static/cache/favicon_500x500.webp +0 -0
- package/template/build/frontend/static/cache/favicon_50x50.webp +0 -0
- package/template/build/frontend/static/cache/gardener_100x100.webp +0 -0
- package/template/build/frontend/static/cache/gardener_500x500.webp +0 -0
- package/template/build/frontend/static/cache/gardener_50x50.webp +0 -0
- package/template/build/frontend/static/components/copybtn.js +0 -99
- package/template/build/frontend/static/components/footer.js +0 -33
- package/template/build/frontend/static/components/gardener/errorBox.js +0 -47
- package/template/build/frontend/static/components/gardener/hotReloadbtn.js +0 -82
- package/template/build/frontend/static/components/gardener/pageOverlayBtn.js +0 -138
- package/template/build/frontend/static/components/gardener/parserWindow.js +0 -159
- package/template/build/frontend/static/components/nonui/api.js +0 -52
- package/template/build/frontend/static/components/nonui/navigation.js +0 -59
- package/template/build/frontend/static/components/notification.js +0 -67
- package/template/build/frontend/static/gardener.js +0 -160
- package/template/build/frontend/static/gardenerConfig.js +0 -1
- package/template/build/frontend/static/gardenerDev.js +0 -165
- package/template/build/frontend/static/global.js +0 -4
- package/template/build/frontend/static/pages/pages._.js +0 -25
- package/template/build/frontend/static/pages/pages._about.js +0 -2
- package/template/build/frontend/static/pages/pages._kartik.js +0 -2
- package/template/build/frontend/static/pages/pages._new.js +0 -2
- package/template/build/frontend/static/pages/pages._ritish.js +0 -2
- package/template/build/frontend/static/style.css +0 -2
- package/template/build/frontend/static/style2.css +0 -26
- package/template/build/frontend/style.css +0 -1045
- package/template/build/frontend/tailwind.css +0 -1
- package/template/build/frontend/views/_.ejs +0 -125
- package/template/build/frontend/views/_about.ejs +0 -126
- package/template/build/frontend/views/_kartik.ejs +0 -126
- package/template/build/frontend/views/_new.ejs +0 -126
- package/template/build/frontend/views/_ritish.ejs +0 -126
- package/template/build/frontend/views/partials/icons/clipboard.ejs +0 -1
- package/template/build/frontend/views/partials/icons/clipboardok.ejs +0 -1
- package/template/src/frontend/static/cache/favicon_500x500.webp +0 -0
- package/template/src/frontend/static/cache/favicon_50x50.webp +0 -0
- package/template/src/frontend/static/cache/gardener_100x100.webp +0 -0
- package/template/src/frontend/static/cache/gardener_500x500.webp +0 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import type { Request, Response } from 'express';
|
|
4
|
+
|
|
5
|
+
// ── Version token ─────────────────────────────────────────────
|
|
6
|
+
// Monotonically-increasing timestamp updated whenever a file
|
|
7
|
+
// in src/frontend/ changes. The frontend stores the first value
|
|
8
|
+
// it sees and reloads as soon as it receives a different one.
|
|
9
|
+
let version: number = Date.now();
|
|
10
|
+
|
|
11
|
+
// ── File watcher ──────────────────────────────────────────────
|
|
12
|
+
const watchTarget = path.resolve('src', 'frontend');
|
|
13
|
+
|
|
14
|
+
let debounce: ReturnType<typeof setTimeout> | null = null;
|
|
15
|
+
|
|
16
|
+
fs.watch(watchTarget, { recursive: true }, (_event, filename) => {
|
|
17
|
+
// Ignore hidden files and node_modules
|
|
18
|
+
if (!filename || filename.startsWith('.')) return;
|
|
19
|
+
|
|
20
|
+
if (debounce) clearTimeout(debounce);
|
|
21
|
+
debounce = setTimeout(() => {
|
|
22
|
+
version = Date.now();
|
|
23
|
+
// console.log(`[gardener] file changed: ${filename} → version ${version}`);
|
|
24
|
+
}, 100);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
console.log(`[gardener] watching ${watchTarget} for changes…`);
|
|
28
|
+
|
|
29
|
+
// ── Route handler ─────────────────────────────────────────────
|
|
30
|
+
// GET /__gardener/hot-reload
|
|
31
|
+
// Returns { version: <number> }
|
|
32
|
+
export function hotReloadHandler(req: Request, res: Response) {
|
|
33
|
+
res.json({ version });
|
|
34
|
+
}
|
|
@@ -3,30 +3,87 @@ import fsp from "fs/promises";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
import generateWebP from "../../libs/generateWebp.js";
|
|
5
5
|
|
|
6
|
-
|
|
7
6
|
import { fileURLToPath } from "url";
|
|
8
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
8
|
const __dirname = path.dirname(__filename);
|
|
10
9
|
|
|
10
|
+
// Whitelist of allowed remote image domains.
|
|
11
|
+
// Set GARDENER_IMAGE_DOMAINS=example.com,cdn.mysite.com in your .env
|
|
12
|
+
function getAllowedDomains(): string[] {
|
|
13
|
+
const raw = process.env.GARDENER_IMAGE_DOMAINS ?? "";
|
|
14
|
+
return raw
|
|
15
|
+
.split(",")
|
|
16
|
+
.map((d) => d.trim().toLowerCase())
|
|
17
|
+
.filter(Boolean);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function isAllowedUrl(rawUrl: string): { ok: true } | { ok: false; reason: string } {
|
|
21
|
+
let parsed: URL;
|
|
22
|
+
try {
|
|
23
|
+
parsed = new URL(rawUrl);
|
|
24
|
+
} catch {
|
|
25
|
+
return { ok: false, reason: "Malformed URL." };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
29
|
+
return { ok: false, reason: "Only http/https URLs are allowed." };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const allowedDomains = getAllowedDomains();
|
|
33
|
+
if (allowedDomains.length === 0) {
|
|
34
|
+
return { ok: false, reason: "No allowed image domains configured. Set GARDENER_IMAGE_DOMAINS in your .env" };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
38
|
+
const isAllowed = allowedDomains.some(
|
|
39
|
+
(domain) => hostname === domain || hostname.endsWith(`.${domain}`)
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
if (!isAllowed) {
|
|
43
|
+
return { ok: false, reason: `Domain '${hostname}' is not in the allowed list.` };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { ok: true };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function downloadRemoteImage(
|
|
50
|
+
remoteUrl: string,
|
|
51
|
+
destPath: string
|
|
52
|
+
): Promise<void> {
|
|
53
|
+
const response = await fetch(remoteUrl);
|
|
54
|
+
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
throw new Error(`Remote fetch failed: ${response.status} ${response.statusText}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
60
|
+
if (!contentType.startsWith("image/")) {
|
|
61
|
+
throw new Error(`Remote URL did not return an image (got: ${contentType})`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const buffer = await response.arrayBuffer();
|
|
65
|
+
await fsp.writeFile(destPath, Buffer.from(buffer));
|
|
66
|
+
}
|
|
11
67
|
|
|
12
68
|
export async function imageOptimiser(req: Request, res: Response) {
|
|
13
69
|
try {
|
|
14
70
|
const { name } = req.params;
|
|
15
71
|
|
|
16
|
-
if (typeof name !==
|
|
17
|
-
|
|
18
|
-
|
|
72
|
+
if (typeof name !== "string") {
|
|
73
|
+
return res.status(400).json({ success: false, message: "invalid path" });
|
|
74
|
+
}
|
|
19
75
|
|
|
76
|
+
// name format: baseName_{width}x{height}.webp
|
|
77
|
+
const match = name.match(/^(.+?)_(\d+)x(\d+)\.webp$/);
|
|
20
78
|
if (!match) {
|
|
21
|
-
return res.status(400).json({ error: "Invalid image format" });
|
|
79
|
+
return res.status(400).json({ error: "Invalid image format. Expected: name_{w}x{h}.webp" });
|
|
22
80
|
}
|
|
23
81
|
|
|
24
82
|
const [, baseName, widthStr, heightStr] = match;
|
|
83
|
+
const width = parseInt(widthStr!, 10);
|
|
84
|
+
const height = parseInt(heightStr!, 10);
|
|
25
85
|
|
|
26
|
-
if
|
|
27
|
-
const width = parseInt(widthStr, 10);
|
|
28
|
-
const height = parseInt(heightStr, 10);
|
|
29
|
-
|
|
86
|
+
// ── 1. Serve from cache if already converted ────────────────────────────
|
|
30
87
|
const cacheDir = path.join(__dirname, "..", "..", "..", "frontend", "static", "cache");
|
|
31
88
|
await fsp.mkdir(cacheDir, { recursive: true });
|
|
32
89
|
|
|
@@ -34,34 +91,66 @@ export async function imageOptimiser(req: Request, res: Response) {
|
|
|
34
91
|
|
|
35
92
|
try {
|
|
36
93
|
await fsp.access(outputPath);
|
|
37
|
-
return res.sendFile(path.basename(outputPath), {
|
|
38
|
-
root: path.dirname(outputPath),
|
|
39
|
-
});
|
|
94
|
+
return res.sendFile(path.basename(outputPath), { root: path.dirname(outputPath) });
|
|
40
95
|
} catch {
|
|
41
96
|
// not cached → continue
|
|
42
97
|
}
|
|
43
98
|
|
|
44
|
-
|
|
45
|
-
const
|
|
99
|
+
// ── 2. Locate source image in assets/ (local) ───────────────────────────
|
|
100
|
+
const assetsDir = path.resolve(__dirname, "..", "..", "..", "frontend", "assets");
|
|
101
|
+
await fsp.mkdir(assetsDir, { recursive: true });
|
|
46
102
|
|
|
47
|
-
|
|
48
|
-
const parsed = path.parse(file);
|
|
49
|
-
return parsed.name === baseName;
|
|
50
|
-
});
|
|
103
|
+
let inputPath: string | null = null;
|
|
51
104
|
|
|
52
|
-
|
|
53
|
-
|
|
105
|
+
const localFiles = await fsp.readdir(assetsDir);
|
|
106
|
+
const localMatch = localFiles.find((file) => path.parse(file).name === baseName);
|
|
107
|
+
if (localMatch) {
|
|
108
|
+
inputPath = path.join(assetsDir, localMatch);
|
|
54
109
|
}
|
|
55
110
|
|
|
56
|
-
|
|
111
|
+
// ── 3. Check assets/remote/ if not found locally ────────────────────────
|
|
112
|
+
const remoteAssetsDir = path.join(assetsDir, "remote");
|
|
113
|
+
await fsp.mkdir(remoteAssetsDir, { recursive: true });
|
|
114
|
+
|
|
115
|
+
if (!inputPath) {
|
|
116
|
+
const remoteFiles = await fsp.readdir(remoteAssetsDir);
|
|
117
|
+
const remoteMatch = remoteFiles.find((file) => path.parse(file).name === baseName);
|
|
118
|
+
if (remoteMatch) {
|
|
119
|
+
inputPath = path.join(remoteAssetsDir, remoteMatch);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ── 4. Download from remote URL if provided and still not found ──────────
|
|
124
|
+
if (!inputPath) {
|
|
125
|
+
const remoteUrl = req.query.url;
|
|
126
|
+
|
|
127
|
+
if (typeof remoteUrl !== "string" || !remoteUrl) {
|
|
128
|
+
return res.status(404).json({
|
|
129
|
+
error: "Source image not found. Provide ?url=<imageUrl> to use a remote source.",
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const urlCheck = isAllowedUrl(remoteUrl);
|
|
134
|
+
if (!urlCheck.ok) {
|
|
135
|
+
return res.status(400).json({ error: urlCheck.reason });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Infer extension from URL (fallback to .jpg)
|
|
139
|
+
const urlPathname = new URL(remoteUrl).pathname;
|
|
140
|
+
const remoteExt = path.extname(urlPathname) || ".jpg";
|
|
141
|
+
const destFilename = `${baseName}${remoteExt}`;
|
|
142
|
+
const destPath = path.join(remoteAssetsDir, destFilename);
|
|
143
|
+
|
|
144
|
+
await downloadRemoteImage(remoteUrl, destPath);
|
|
145
|
+
inputPath = destPath;
|
|
146
|
+
}
|
|
57
147
|
|
|
148
|
+
// ── 5. Convert & cache ───────────────────────────────────────────────────
|
|
58
149
|
await generateWebP(inputPath, outputPath, width, height);
|
|
59
150
|
|
|
60
|
-
return res.sendFile(path.basename(outputPath), {
|
|
61
|
-
root: path.dirname(outputPath),
|
|
62
|
-
});
|
|
151
|
+
return res.sendFile(path.basename(outputPath), { root: path.dirname(outputPath) });
|
|
63
152
|
} catch (err) {
|
|
64
153
|
console.error(err);
|
|
65
|
-
return res.status(500).json({ error: "Image optimisation failed" });
|
|
154
|
+
return res.status(500).json({ error: "Image optimisation failed", detail: String(err) });
|
|
66
155
|
}
|
|
67
156
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Request, Response } from 'express';
|
|
2
2
|
import { Router } from "express";
|
|
3
|
-
import { addComponent, addPage, imageOptimiser, saveTemplate } from "../controllers/gardener/index.js";
|
|
3
|
+
import { addComponent, addPage, imageOptimiser, saveTemplate, hotReloadHandler } from "../controllers/gardener/index.js";
|
|
4
4
|
|
|
5
5
|
const router: Router = Router();
|
|
6
6
|
export default router;
|
|
@@ -14,6 +14,7 @@ if (process.env.NODE_ENV !== 'production') {
|
|
|
14
14
|
router.route('/addcomponent').post(addComponent);
|
|
15
15
|
router.route('/addpage').post(addPage);
|
|
16
16
|
router.route('/savetemplate').post(saveTemplate);
|
|
17
|
+
router.route('/__gardener/hot-reload').get(hotReloadHandler);
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// server.ts
|
|
2
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
2
3
|
import 'dotenv/config';
|
|
3
4
|
import express from 'express';
|
|
4
5
|
import frontendRoute from './routes/gardener.route.js'
|
|
6
|
+
|
|
5
7
|
import path from "path";
|
|
6
8
|
|
|
7
9
|
const app = express();
|
|
@@ -15,10 +17,26 @@ const staticFiles = path.resolve(__dirname, '..', 'frontend')
|
|
|
15
17
|
|
|
16
18
|
app.set('views', path.join(staticFiles, 'views'));
|
|
17
19
|
app.set("view engine", "ejs");
|
|
18
|
-
app.use(express.static(staticFiles
|
|
20
|
+
app.use(express.static(staticFiles,
|
|
21
|
+
{
|
|
22
|
+
maxAge: '1y', // 1 year
|
|
23
|
+
immutable: true
|
|
24
|
+
}
|
|
25
|
+
));
|
|
26
|
+
|
|
19
27
|
app.use(express.json());
|
|
20
28
|
app.use(frontendRoute);
|
|
21
29
|
|
|
30
|
+
|
|
31
|
+
app.use((err: Error & { status: number }, req: Request, res: Response, next: NextFunction) => {
|
|
32
|
+
console.error(err.stack);
|
|
33
|
+
|
|
34
|
+
res.status(err.status || 500).json({
|
|
35
|
+
success: false,
|
|
36
|
+
message: err.message || "Internal Server Error",
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
22
40
|
const PORT = process.env.PORT || 3000;
|
|
23
41
|
|
|
24
42
|
app.listen(PORT, () => {
|
|
Binary file
|
|
@@ -1,82 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
if (
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
hotReload = !hotReload;
|
|
24
|
-
|
|
25
|
-
if (hr) {
|
|
26
|
-
hrcheck.style.background = '#66e666';
|
|
27
|
-
fetchElement('.hrcheckbox').checked = true;
|
|
28
|
-
localStorage.setItem('hotreload', 'true');
|
|
29
|
-
hotReloadtimeout = setTimeout(() => window.location.reload(), 1000);
|
|
1
|
+
const POLL_INTERVAL = 500; // ms
|
|
2
|
+
const ENDPOINT = '/__gardener/hot-reload';
|
|
3
|
+
|
|
4
|
+
let knownVersion = null;
|
|
5
|
+
|
|
6
|
+
async function poll() {
|
|
7
|
+
try {
|
|
8
|
+
const res = await fetch(ENDPOINT);
|
|
9
|
+
if (!res.ok) return;
|
|
10
|
+
const { version } = await res.json();
|
|
11
|
+
|
|
12
|
+
if (knownVersion === null) {
|
|
13
|
+
knownVersion = version; // capture baseline on first poll
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (version !== knownVersion) {
|
|
18
|
+
window.location.reload();
|
|
19
|
+
}
|
|
20
|
+
} catch {
|
|
21
|
+
// network hiccup — retry next tick
|
|
30
22
|
}
|
|
31
|
-
else {
|
|
32
|
-
hrcheck.style.background = 'red';
|
|
33
|
-
fetchElement('.hrcheckbox').checked = false;
|
|
34
|
-
localStorage.setItem('hotreload', 'false');
|
|
35
|
-
clearTimeout(hotReloadtimeout);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
//localStorage.setItem('hotreload', hotReload);
|
|
39
23
|
}
|
|
40
24
|
|
|
41
|
-
export function
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
cn: ['bg-gray-200', 'fixed', 'bottom-0', 'z-100', 'right-0', 'border-b-1', 'p-2', 'rounded-md'],
|
|
45
|
-
children: [
|
|
46
|
-
{
|
|
47
|
-
t: 'span',
|
|
48
|
-
txt: 'Press '
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
t: 'span',
|
|
52
|
-
cn: ['text-green-500', 'font-bold'],
|
|
53
|
-
txt: 'Alt+h'
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
t: 'span',
|
|
57
|
-
txt: ' to toggle Hot Reload'
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
t: 'form',
|
|
61
|
-
attr: {
|
|
62
|
-
id: 'hrcheckbox',
|
|
63
|
-
},
|
|
64
|
-
events: {
|
|
65
|
-
click: () => togglehotreload()
|
|
66
|
-
},
|
|
67
|
-
cn: ['p-2', 'bg-red-300'],
|
|
68
|
-
children: [{
|
|
69
|
-
t: 'label',
|
|
70
|
-
txt: 'Hot Reload ',
|
|
71
|
-
}
|
|
72
|
-
, {
|
|
73
|
-
t: 'input',
|
|
74
|
-
cn: ['hrcheckbox'],
|
|
75
|
-
attr: {
|
|
76
|
-
type: 'checkbox'
|
|
77
|
-
}
|
|
78
|
-
}]
|
|
79
|
-
}
|
|
80
|
-
]
|
|
81
|
-
})
|
|
25
|
+
export function startHotReload() {
|
|
26
|
+
poll(); // immediate check
|
|
27
|
+
setInterval(poll, POLL_INTERVAL);
|
|
82
28
|
}
|
|
29
|
+
|
|
@@ -2,7 +2,7 @@ import { mode } from './gardenerConfig.js'
|
|
|
2
2
|
import { gardener, appendElement, fetchElement } from './gardener.js'
|
|
3
3
|
import { addPagebtn } from './components/gardener/pageOverlayBtn.js';
|
|
4
4
|
import { parserWindow as parserWindowComponent } from './components/gardener/parserWindow.js';
|
|
5
|
-
import {
|
|
5
|
+
import { startHotReload } from './components/gardener/hotReloadbtn.js';
|
|
6
6
|
import { gardenerError } from './components/gardener/errorBox.js';
|
|
7
7
|
|
|
8
8
|
const body = fetchElement('body');
|
|
@@ -20,28 +20,12 @@ const body = fetchElement('body');
|
|
|
20
20
|
|
|
21
21
|
if (mode === 'dev') {
|
|
22
22
|
appendElement(body, addPagebtn);
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
appendElement(body, hotReloadBtn())
|
|
26
|
-
|
|
27
|
-
togglehotreload();
|
|
23
|
+
startHotReload();
|
|
28
24
|
}
|
|
29
25
|
|
|
30
26
|
|
|
31
27
|
|
|
32
28
|
|
|
33
|
-
document.addEventListener('keydown', function(e) {
|
|
34
|
-
// Detect Ctrl + H
|
|
35
|
-
if (e.altKey && e.key.toLowerCase() === 'h') {
|
|
36
|
-
e.preventDefault(); // Stop browser from opening history
|
|
37
|
-
// Your logic here...
|
|
38
|
-
togglehotreload();
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
29
|
|
|
46
30
|
|
|
47
31
|
export function parserWindow(text) {
|
|
@@ -250,18 +250,12 @@
|
|
|
250
250
|
.top-0 {
|
|
251
251
|
top: calc(var(--spacing) * 0);
|
|
252
252
|
}
|
|
253
|
-
.top-1 {
|
|
254
|
-
top: calc(var(--spacing) * 1);
|
|
255
|
-
}
|
|
256
253
|
.top-1\/2 {
|
|
257
254
|
top: calc(1 / 2 * 100%);
|
|
258
255
|
}
|
|
259
256
|
.top-1\/4 {
|
|
260
257
|
top: calc(1 / 4 * 100%);
|
|
261
258
|
}
|
|
262
|
-
.top-2 {
|
|
263
|
-
top: calc(var(--spacing) * 2);
|
|
264
|
-
}
|
|
265
259
|
.top-2\/5 {
|
|
266
260
|
top: calc(2 / 5 * 100%);
|
|
267
261
|
}
|
|
@@ -277,9 +271,6 @@
|
|
|
277
271
|
.right-4 {
|
|
278
272
|
right: calc(var(--spacing) * 4);
|
|
279
273
|
}
|
|
280
|
-
.bottom-0 {
|
|
281
|
-
bottom: calc(var(--spacing) * 0);
|
|
282
|
-
}
|
|
283
274
|
.bottom-4 {
|
|
284
275
|
bottom: calc(var(--spacing) * 4);
|
|
285
276
|
}
|
|
@@ -292,18 +283,12 @@
|
|
|
292
283
|
.left-0 {
|
|
293
284
|
left: calc(var(--spacing) * 0);
|
|
294
285
|
}
|
|
295
|
-
.left-1 {
|
|
296
|
-
left: calc(var(--spacing) * 1);
|
|
297
|
-
}
|
|
298
286
|
.left-1\/2 {
|
|
299
287
|
left: calc(1 / 2 * 100%);
|
|
300
288
|
}
|
|
301
289
|
.left-1\/4 {
|
|
302
290
|
left: calc(1 / 4 * 100%);
|
|
303
291
|
}
|
|
304
|
-
.left-2 {
|
|
305
|
-
left: calc(var(--spacing) * 2);
|
|
306
|
-
}
|
|
307
292
|
.left-2\/5 {
|
|
308
293
|
left: calc(2 / 5 * 100%);
|
|
309
294
|
}
|
|
@@ -319,9 +304,6 @@
|
|
|
319
304
|
.z-90 {
|
|
320
305
|
z-index: 90;
|
|
321
306
|
}
|
|
322
|
-
.z-100 {
|
|
323
|
-
z-index: 100;
|
|
324
|
-
}
|
|
325
307
|
.z-\[100\] {
|
|
326
308
|
z-index: 100;
|
|
327
309
|
}
|
|
@@ -379,12 +361,6 @@
|
|
|
379
361
|
.inline-block {
|
|
380
362
|
display: inline-block;
|
|
381
363
|
}
|
|
382
|
-
.table {
|
|
383
|
-
display: table;
|
|
384
|
-
}
|
|
385
|
-
.h-2 {
|
|
386
|
-
height: calc(var(--spacing) * 2);
|
|
387
|
-
}
|
|
388
364
|
.h-2\/4 {
|
|
389
365
|
height: calc(2 / 4 * 100%);
|
|
390
366
|
}
|
|
@@ -403,24 +379,15 @@
|
|
|
403
379
|
.h-screen {
|
|
404
380
|
height: 100vh;
|
|
405
381
|
}
|
|
406
|
-
.w-1 {
|
|
407
|
-
width: calc(var(--spacing) * 1);
|
|
408
|
-
}
|
|
409
382
|
.w-1\/4 {
|
|
410
383
|
width: calc(1 / 4 * 100%);
|
|
411
384
|
}
|
|
412
|
-
.w-2 {
|
|
413
|
-
width: calc(var(--spacing) * 2);
|
|
414
|
-
}
|
|
415
385
|
.w-2\/4 {
|
|
416
386
|
width: calc(2 / 4 * 100%);
|
|
417
387
|
}
|
|
418
388
|
.w-5 {
|
|
419
389
|
width: calc(var(--spacing) * 5);
|
|
420
390
|
}
|
|
421
|
-
.w-11 {
|
|
422
|
-
width: calc(var(--spacing) * 11);
|
|
423
|
-
}
|
|
424
391
|
.w-11\/12 {
|
|
425
392
|
width: calc(11 / 12 * 100%);
|
|
426
393
|
}
|
|
@@ -457,21 +424,10 @@
|
|
|
457
424
|
.flex-1 {
|
|
458
425
|
flex: 1;
|
|
459
426
|
}
|
|
460
|
-
.border-collapse {
|
|
461
|
-
border-collapse: collapse;
|
|
462
|
-
}
|
|
463
|
-
.-translate-x-1 {
|
|
464
|
-
--tw-translate-x: calc(var(--spacing) * -1);
|
|
465
|
-
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
466
|
-
}
|
|
467
427
|
.-translate-x-1\/2 {
|
|
468
428
|
--tw-translate-x: calc(calc(1 / 2 * 100%) * -1);
|
|
469
429
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
470
430
|
}
|
|
471
|
-
.-translate-y-1 {
|
|
472
|
-
--tw-translate-y: calc(var(--spacing) * -1);
|
|
473
|
-
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
474
|
-
}
|
|
475
431
|
.-translate-y-1\/2 {
|
|
476
432
|
--tw-translate-y: calc(calc(1 / 2 * 100%) * -1);
|
|
477
433
|
translate: var(--tw-translate-x) var(--tw-translate-y);
|
|
@@ -482,9 +438,6 @@
|
|
|
482
438
|
.cursor-pointer {
|
|
483
439
|
cursor: pointer;
|
|
484
440
|
}
|
|
485
|
-
.resize {
|
|
486
|
-
resize: both;
|
|
487
|
-
}
|
|
488
441
|
.grid-cols-2 {
|
|
489
442
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
490
443
|
}
|
|
@@ -546,9 +499,6 @@
|
|
|
546
499
|
.rounded-lg {
|
|
547
500
|
border-radius: var(--radius-lg);
|
|
548
501
|
}
|
|
549
|
-
.rounded-md {
|
|
550
|
-
border-radius: var(--radius-md);
|
|
551
|
-
}
|
|
552
502
|
.rounded-xl {
|
|
553
503
|
border-radius: var(--radius-xl);
|
|
554
504
|
}
|
|
@@ -572,10 +522,6 @@
|
|
|
572
522
|
border-top-style: var(--tw-border-style);
|
|
573
523
|
border-top-width: 4px;
|
|
574
524
|
}
|
|
575
|
-
.border-b-1 {
|
|
576
|
-
border-bottom-style: var(--tw-border-style);
|
|
577
|
-
border-bottom-width: 1px;
|
|
578
|
-
}
|
|
579
525
|
.border-l-8 {
|
|
580
526
|
border-left-style: var(--tw-border-style);
|
|
581
527
|
border-left-width: 8px;
|
|
@@ -709,9 +655,6 @@
|
|
|
709
655
|
.py-20 {
|
|
710
656
|
padding-block: calc(var(--spacing) * 20);
|
|
711
657
|
}
|
|
712
|
-
.pb-1 {
|
|
713
|
-
padding-bottom: calc(var(--spacing) * 1);
|
|
714
|
-
}
|
|
715
658
|
.pb-1\.5 {
|
|
716
659
|
padding-bottom: calc(var(--spacing) * 1.5);
|
|
717
660
|
}
|
|
@@ -797,9 +740,6 @@
|
|
|
797
740
|
.text-green-400 {
|
|
798
741
|
color: var(--color-green-400);
|
|
799
742
|
}
|
|
800
|
-
.text-green-500 {
|
|
801
|
-
color: var(--color-green-500);
|
|
802
|
-
}
|
|
803
743
|
.text-green-800 {
|
|
804
744
|
color: var(--color-green-800);
|
|
805
745
|
}
|
|
@@ -830,9 +770,6 @@
|
|
|
830
770
|
.italic {
|
|
831
771
|
font-style: italic;
|
|
832
772
|
}
|
|
833
|
-
.underline {
|
|
834
|
-
text-decoration-line: underline;
|
|
835
|
-
}
|
|
836
773
|
.opacity-70 {
|
|
837
774
|
opacity: 70%;
|
|
838
775
|
}
|
|
@@ -852,19 +789,11 @@
|
|
|
852
789
|
--tw-shadow: 0 20px 25px -5px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 8px 10px -6px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
|
|
853
790
|
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
|
|
854
791
|
}
|
|
855
|
-
.outline {
|
|
856
|
-
outline-style: var(--tw-outline-style);
|
|
857
|
-
outline-width: 1px;
|
|
858
|
-
}
|
|
859
792
|
.backdrop-blur-md {
|
|
860
793
|
--tw-backdrop-blur: blur(var(--blur-md));
|
|
861
794
|
-webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
|
|
862
795
|
backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
|
|
863
796
|
}
|
|
864
|
-
.backdrop-filter {
|
|
865
|
-
-webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
|
|
866
|
-
backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);
|
|
867
|
-
}
|
|
868
797
|
.transition {
|
|
869
798
|
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;
|
|
870
799
|
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
|
@@ -1081,11 +1010,6 @@
|
|
|
1081
1010
|
inherits: false;
|
|
1082
1011
|
initial-value: 0 0 #0000;
|
|
1083
1012
|
}
|
|
1084
|
-
@property --tw-outline-style {
|
|
1085
|
-
syntax: "*";
|
|
1086
|
-
inherits: false;
|
|
1087
|
-
initial-value: solid;
|
|
1088
|
-
}
|
|
1089
1013
|
@property --tw-backdrop-blur {
|
|
1090
1014
|
syntax: "*";
|
|
1091
1015
|
inherits: false;
|
|
@@ -1170,7 +1094,6 @@
|
|
|
1170
1094
|
--tw-ring-offset-width: 0px;
|
|
1171
1095
|
--tw-ring-offset-color: #fff;
|
|
1172
1096
|
--tw-ring-offset-shadow: 0 0 #0000;
|
|
1173
|
-
--tw-outline-style: solid;
|
|
1174
1097
|
--tw-backdrop-blur: initial;
|
|
1175
1098
|
--tw-backdrop-brightness: initial;
|
|
1176
1099
|
--tw-backdrop-contrast: initial;
|