create-gardener 1.0.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.
@@ -0,0 +1,54 @@
1
+
2
+ import { gardener } from '../gardener.js'
3
+
4
+ export default function () {
5
+ return gardener({
6
+ "t": "div",
7
+ "attr": {
8
+ "id": "body"
9
+ },
10
+ "children": [
11
+ {
12
+ "t": "div",
13
+ "cn": [
14
+ "h-screen",
15
+ "w-screen",
16
+ "bg-white",
17
+ "loader",
18
+ "absolute"
19
+ ],
20
+ "attr": {
21
+ "style": "transition: 0.4s; opacity: 0;"
22
+ },
23
+ "txt": ""
24
+ },
25
+ {
26
+ "t": "div",
27
+ "cn": [
28
+ "hero",
29
+ "flex",
30
+ "justify-around",
31
+ "items-center",
32
+ "p-5",
33
+ "h-[90vh]"
34
+ ],
35
+ "children": [
36
+ {
37
+ "t": "p",
38
+ "cn": [
39
+ "p-5"
40
+ ],
41
+ "txt": "Gardener is a front-end library for creating and manipulating DOM elements using a declarative JavaScript object syntax. It includes a development server with features like hot-reloading and on-the-fly component creation from existing HTML. The server also provides dynamic image resizing and caching."
42
+ },
43
+ {
44
+ "t": "img",
45
+ "attr": {
46
+ "src": "/cache/w_500x500.webp",
47
+ "alt": "logo"
48
+ }
49
+ }
50
+ ]
51
+ }
52
+ ]
53
+ })
54
+ }
@@ -0,0 +1,404 @@
1
+ const config = {
2
+ mode: 'dev',
3
+ componentdir: 'components',
4
+ hotreload: true
5
+ }
6
+
7
+ let hotReloadtimeout;
8
+ const body = fetchElement('body');
9
+ let hotReload = localStorage.getItem('hotreload');
10
+
11
+ if (hotReload === null) hotReload = config.hotreload;
12
+ else if (hotReload === 'true') hotReload = true
13
+ else if (hotReload === 'false') hotReload = false
14
+
15
+ function opnPagedialog(btn = true) {
16
+ if (btn) {
17
+ const dialog = gardener({
18
+ t: 'form', cn: ['addpageform', 'fixed', 'left-2/5', 'bg-gray-200', 'rounded-lg', 'block', 'top-2/5', 'p-2', 'flex', 'flex-col', 'p-5', 'gap-2'], events: {
19
+ submit: async (e) => {
20
+ try {
21
+ e.preventDefault()
22
+ const data = new FormData(e.target);
23
+ const input = Object.fromEntries(data.entries());
24
+ console.log(input)
25
+
26
+ const response = await fetch('/addpage', {
27
+ method: 'POST',
28
+ headers: {
29
+ "Content-Type": 'application/json'
30
+ },
31
+ body: JSON.stringify(input)
32
+ }).then(res => res.json())
33
+ console.log(response)
34
+ opnPagedialog(false)
35
+ window.location.href = input.page
36
+ }
37
+ catch (err) {
38
+ console.log(err)
39
+ }
40
+
41
+ }
42
+ }, children: [{
43
+ t: 'label',
44
+ txt: 'ENTER PATH FOR NEW PAGE'
45
+ }, { t: 'input', attr: { name: 'page' }, cn: ['pathinput'] }]
46
+
47
+ })
48
+
49
+ console.log('test')
50
+ appendElement(body, dialog);
51
+ fetchElement('.pathinput').focus();
52
+ }
53
+ else {
54
+ console.log('removed')
55
+ fetchElement('.addpageform').remove();
56
+ }
57
+ }
58
+
59
+ if (config.mode === 'dev') {
60
+ const addPagebtn = gardener({
61
+ t: 'button',
62
+ cn: ['pb-1.5', 'flex', 'items-center', 'justify-center', 'h-15', 'w-15', 'bg-green-300', 'fixed', 'bottom-22', 'z-100', 'right-2', 'rounded-full', 'text-5xl'],
63
+ children: [{ t: 'span', txt: '+' }],
64
+ events: {
65
+ click: opnPagedialog
66
+ }
67
+ });
68
+
69
+ appendElement(body, addPagebtn);
70
+
71
+
72
+ appendElement(body, gardener({
73
+ t: 'p',
74
+ cn: ['bg-gray-200', 'fixed', 'bottom-0', 'z-100', 'right-0', 'border-b-1', 'p-2', 'rounded-md'],
75
+ children: [
76
+ {
77
+ t: 'span',
78
+ txt: 'Press '
79
+ },
80
+ {
81
+ t: 'span',
82
+ cn: ['text-green-500', 'font-bold'],
83
+ txt: 'Ctrl+h'
84
+ },
85
+ {
86
+ t: 'span',
87
+ txt: ' to toggle Hot Reload'
88
+ },
89
+ {
90
+ t: 'form',
91
+ attr: {
92
+ id: 'hrcheckbox',
93
+ },
94
+ events: {
95
+ click: () => togglehotreload()
96
+ },
97
+ cn: ['p-2', 'bg-red-300'],
98
+ children: [{
99
+ t: 'label',
100
+ txt: 'Hot Reload ',
101
+ }
102
+ , {
103
+ t: 'input',
104
+ cn: ['hrcheckbox'],
105
+ attr: {
106
+ type: 'checkbox'
107
+ }
108
+ }]
109
+ }
110
+ ]
111
+ }))
112
+
113
+ //appendElement(body, gardener())
114
+
115
+
116
+ togglehotreload();
117
+ document.addEventListener('keydown', function(e) {
118
+ // Detect Ctrl + H
119
+ if (e.ctrlKey && e.key.toLowerCase() === 'h') {
120
+ e.preventDefault(); // Stop browser from opening history
121
+ // Your logic here...
122
+ togglehotreload();
123
+ }
124
+ });
125
+ }
126
+
127
+ //if (config.mode === 'dev') {
128
+
129
+
130
+ function togglehotreload() {
131
+ const hr = hotReload;
132
+ const hrcheck = fetchElement('#hrcheckbox');
133
+
134
+ localStorage.setItem('hotreload', hr);
135
+
136
+ hotReload = !hotReload;
137
+
138
+ if (hr) {
139
+ hrcheck.style.background = '#66e666';
140
+ fetchElement('.hrcheckbox').checked = true;
141
+ localStorage.setItem('hotreload', 'true');
142
+ hotReloadtimeout = setTimeout(() => window.location.reload(), 1000);
143
+ }
144
+ else {
145
+ hrcheck.style.background = 'red';
146
+ fetchElement('.hrcheckbox').checked = false;
147
+ localStorage.setItem('hotreload', 'false');
148
+ clearTimeout(hotReloadtimeout);
149
+ }
150
+
151
+ //localStorage.setItem('hotreload', hotReload);
152
+ }
153
+
154
+ export function parserWindow(text) {
155
+ if (config.mode !== 'dev') return;
156
+
157
+
158
+ const result = gardener({
159
+ t: 'div',
160
+ cn: ['fixed', 'border-2', 'border-black', 'bg-gray-500', 'text-white', 'rounded-lg', 'z-90', 'w-2/4', 'h-2/4', 'left-1/4', 'flex', 'flex-col', 'justify-between', 'top-1/4'],
161
+ children: [
162
+ {
163
+ t: 'div',
164
+ cn: ['bg-gray-200', 'h-15', 'text-black', 'rounded-t-lg', 'flex', 'items-center', 'justify-around'],
165
+ children: [
166
+ {
167
+ t: 'h3',
168
+ cn: ['font-bold'],
169
+ txt: 'Parser Window'
170
+ },
171
+ {
172
+ t: 'button',
173
+ cn: ['p-2', 'bg-red-300', 'rounded-lg', 'cursor-pointer'],
174
+ txt: 'Add Component',
175
+ attr: {
176
+ id: 'copybtn'
177
+ },
178
+ events: {
179
+ click: copytxt
180
+ }
181
+ }
182
+ ]
183
+ },
184
+ {
185
+ t: 'p',
186
+ cn: ['p-5', 'overflow-scroll'],
187
+ txt: text
188
+ },
189
+ ]
190
+ })
191
+
192
+ function copytxt() {
193
+ result.remove()
194
+
195
+ const compform = gardener({
196
+ t: 'form',
197
+ events: {
198
+ submit: (event) => {
199
+ event.preventDefault()
200
+ copyText(text, `${fetchElement('.componentInp').value}.js`)
201
+ compform.remove();
202
+ }
203
+ },
204
+ cn: ['fixed', 'left-2/5', 'bg-gray-500', 'rounded-lg', 'block', 'top-2/5', 'p-2'],
205
+ children: [
206
+ {
207
+ t: 'input',
208
+ cn: ['bg-white', 'componentInp'],
209
+ attr: {
210
+ type: 'text',
211
+ placeholder: 'Component Name'
212
+ }
213
+ }
214
+ ]
215
+ });
216
+ appendElement(body, compform);
217
+
218
+ fetchElement('.componentInp').focus();
219
+ //setTimeout(() => result.remove(), 500);
220
+ }
221
+
222
+ appendElement(body, result);
223
+ }
224
+
225
+
226
+
227
+ async function copyText(txt, path) {
228
+ // await navigator.clipboard.writeText(txt);
229
+ try {
230
+ const res = await fetch('/addcomponent', {
231
+ method: 'POST',
232
+ headers: {
233
+ "Content-Type": 'application/json'
234
+ },
235
+ body: JSON.stringify({ component: txt, path: `${config.componentdir}/${path}` })
236
+ })
237
+
238
+ if (!res.ok) console.error('wrong');
239
+
240
+ const data = await res.json()
241
+ console.log(data);
242
+
243
+ }
244
+ catch (err) {
245
+ console.error(err);
246
+ }
247
+
248
+ }
249
+
250
+
251
+
252
+
253
+ export function fetchElement(param) {
254
+ return document.querySelector(param);
255
+ }
256
+
257
+ export function appendElement(parent, child) {
258
+ parent.appendChild(child);
259
+ }
260
+
261
+ export function createElement(type, classname) {
262
+ let element = document.createElement(type);
263
+ if (classname)
264
+ element.classList.add(...classname);
265
+ return element;
266
+ }
267
+
268
+ export function insertText(element, text) {
269
+ element.innerText = text;
270
+ }
271
+
272
+ export function replaceElement(original, New) {
273
+ original.replaceWith(New);
274
+ }
275
+
276
+ export function gardener(Dom) {
277
+
278
+ if (Dom.nodeType === 1) return Dom;
279
+ // detect if this is an SVG element
280
+ const isSVG = [
281
+ 'svg', 'path', 'circle', 'rect', 'line', 'polygon', 'polyline', 'g', 'defs', 'clipPath', 'use'
282
+ ].includes(Dom.t);
283
+
284
+ // create element accordingly
285
+ let element;
286
+
287
+ if (isSVG) {
288
+ element = document.createElementNS('http://www.w3.org/2000/svg', Dom.t);
289
+ if (Dom.cn)
290
+ element.classList.add(...Dom.cn);
291
+ }
292
+ else {
293
+ element = createElement(Dom.t, Dom.cn);
294
+ }
295
+
296
+ // text content (skip for SVG like <path>)
297
+ if (Dom.txt) {
298
+ insertText(element, Dom.txt);
299
+ }
300
+
301
+ // apply attributes safely
302
+ const propertyNames = new Set([
303
+ 'value', 'selected', 'muted', 'disabled',
304
+ 'selectedIndex', 'volume', // etc.
305
+ ]);
306
+
307
+ if (Dom.attr) {
308
+ for (const [key, value] of Object.entries(Dom.attr)) {
309
+ if (isSVG || key.startsWith('data-') || key.startsWith('aria-')) {
310
+ element.setAttribute(key, value);
311
+ } else if (key in element && !propertyNames.has(key)) {
312
+ // Prefer property for known safe cases
313
+ try { element[key] = value === '' ? true : value; } catch (e) { element.setAttribute(key, value); }
314
+ } else {
315
+ element.setAttribute(key, value);
316
+ }
317
+ }
318
+ }
319
+
320
+ if (Dom.events) {
321
+ Object.entries(Dom.events).forEach(([eventName, handler]) => {
322
+ element.addEventListener(eventName, handler);
323
+ });
324
+ }
325
+
326
+ // recursively handle children
327
+ if (Dom.children) {
328
+ Dom.children.forEach(child => appendElement(element, gardener(child)));
329
+ }
330
+
331
+ return element;
332
+ }
333
+
334
+ export function parser(element, isParent = true) {
335
+ if (typeof element === 'string') {
336
+ // If user passes raw HTML string
337
+ const temp = document.createElement('div');
338
+ temp.innerHTML = element.trim();
339
+ element = temp.firstElementChild;
340
+ }
341
+
342
+ const obj = {
343
+ t: element.tagName.toLowerCase(),
344
+ };
345
+
346
+ // add classes if present
347
+ if (element.classList.length) {
348
+ obj.cn = Array.from(element.classList);
349
+ }
350
+
351
+ // add attributes if present
352
+ const attrs = {};
353
+ for (const attr of element.attributes) {
354
+ if (attr.name !== 'class') attrs[attr.name] = attr.value;
355
+ }
356
+ if (Object.keys(attrs).length) obj.attr = attrs;
357
+ // add text content (only if no children)
358
+ if (element.childNodes.length === 1 && element.firstChild.nodeType === Node.TEXT_NODE) {
359
+ obj.txt = element.textContent.trim();
360
+
361
+ if (isParent) {
362
+ parserWindow(JSON.stringify(obj))
363
+ }
364
+
365
+ return obj;
366
+ }
367
+
368
+ // add children recursively
369
+ const children = [];
370
+ for (const child of element.children) {
371
+ children.push(parser(child, false));
372
+ }
373
+ if (children.length) obj.children = children;
374
+
375
+
376
+ if (isParent) {
377
+ parserWindow(JSON.stringify(obj))
378
+ }
379
+
380
+ return obj
381
+ //Let Browser do the migration from html to json and then use copy paste
382
+ }
383
+
384
+ export function imagePreloader(images) {
385
+ const body = fetchElement('body')
386
+ images.forEach(entry => {
387
+ appendElement(body, gardener({
388
+ t: 'img',
389
+ cn: ['preloaderimage'],
390
+ attr: {
391
+ src: entry,
392
+ alt: entry
393
+ }
394
+ }));
395
+
396
+ setTimeout(() => {
397
+ const images = document.querySelectorAll('.preloaderimage');
398
+ images.forEach(entry => { entry.style.display = 'none' });
399
+ }, 0)
400
+
401
+ })
402
+ }
403
+
404
+
@@ -0,0 +1,57 @@
1
+ import { parser, fetchElement, replaceElement, gardener, appendElement } from './gardener.js'
2
+
3
+ const body = fetchElement('#body')
4
+
5
+ nextPagehandler();
6
+ pageloader();
7
+
8
+
9
+ function nextPagehandler() {
10
+ const anchor = document.querySelectorAll('a')
11
+ anchor.forEach(link => {
12
+
13
+ link.addEventListener('click', (e) => {
14
+
15
+ //event delegation
16
+ const link = e.target.closest('a');
17
+ if (!link) return;
18
+ if (link.target === '_blank') return;
19
+ //event delegation
20
+
21
+ e.preventDefault();
22
+ appendElement(body, gardener({
23
+ t: 'div',
24
+ cn: ['tempnpdiv', 'top-0', 'left-[100vw]', 'absolute', 'h-screen', 'w-screen', 'shadow-[30px_0_60px_15px_rgb(0,0,0)]'],
25
+ }))
26
+ const width = window.innerWidth
27
+ console.log(width)
28
+ body.style.transition = '.2s';
29
+ body.style.transform = `translateX(-${width}px)`
30
+ setTimeout(() => {
31
+ window.location.href = link.href
32
+ }, 200)
33
+ })
34
+ })
35
+
36
+ window.addEventListener('pagehide', () => {
37
+ setTimeout(() => {
38
+ body.style.transform = 'translateX(0px)';
39
+ setTimeout(() => {
40
+ fetchElement('.tempnpdiv').remove()
41
+
42
+ }, 200)
43
+ }, 200);
44
+ });
45
+
46
+ }
47
+
48
+ function pageloader() {
49
+ const loader = fetchElement('.loader');
50
+ loader.style.transition = '.4s';
51
+ loader.style.opacity = '0';
52
+ setTimeout(() => loader.remove(), 400)
53
+
54
+ }
55
+ //console.log('hellooo');
56
+ //parser(fetchElement('.hero'));
57
+ //parser(fetchElement('nav'));