Updated NJET
This commit is contained in:
		
							parent
							
								
									62eb1060d9
								
							
						
					
					
						commit
						fbd72f727a
					
				
							
								
								
									
										473
									
								
								src/snek/static/njet.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										473
									
								
								src/snek/static/njet.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,473 @@ | ||||
| 
 | ||||
| 
 | ||||
| class RestClient { | ||||
|   constructor({ baseURL = '', headers = {} } = {}) { | ||||
|     this.baseURL = baseURL; | ||||
|     this.headers = { ...headers }; | ||||
| 
 | ||||
|     // Interceptor containers
 | ||||
|     this.interceptors = { | ||||
|       request: { | ||||
|         handlers: [], | ||||
|         use: (fn) => { | ||||
|           this.interceptors.request.handlers.push(fn); | ||||
|         } | ||||
|       }, | ||||
|       response: { | ||||
|         handlers: [], | ||||
|         use: (successFn, errorFn) => { | ||||
|           this.interceptors.response.handlers.push({ success: successFn, error: errorFn }); | ||||
|         } | ||||
|       } | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   // Core request method
 | ||||
|   request(config) { | ||||
|     // Merge defaults
 | ||||
|     const cfg = { | ||||
|       method: 'GET', | ||||
|       url: '', | ||||
|       data: null, | ||||
|       headers: {}, | ||||
|       ...config | ||||
|     }; | ||||
|     cfg.headers = { ...this.headers, ...cfg.headers }; | ||||
| 
 | ||||
|     // Apply request interceptors
 | ||||
|     let chain = Promise.resolve(cfg); | ||||
|     this.interceptors.request.handlers.forEach((fn) => { | ||||
|       chain = chain.then(fn); | ||||
|     }); | ||||
| 
 | ||||
|     // Perform fetch
 | ||||
|     chain = chain.then((c) => { | ||||
|       const url = this.baseURL + c.url; | ||||
|       const options = { method: c.method, headers: c.headers }; | ||||
|       if (c.data != null) { | ||||
|         options.body = JSON.stringify(c.data); | ||||
|         if (!options.headers['Content-Type']) { | ||||
|           options.headers['Content-Type'] = 'application/json'; | ||||
|         } | ||||
|       } | ||||
|       return fetch(url, options).then(async (response) => { | ||||
|         const text = await response.text(); | ||||
|         let data; | ||||
|         try { | ||||
|           data = JSON.parse(text); | ||||
|         } catch (e) { | ||||
|           data = text; | ||||
|         } | ||||
|         const result = { | ||||
|           data, | ||||
|           status: response.status, | ||||
|           statusText: response.statusText, | ||||
|           headers: RestClient._parseHeaders(response.headers), | ||||
|           config: c, | ||||
|           request: response | ||||
|         }; | ||||
|         if (!response.ok) { | ||||
|           return Promise.reject(result); | ||||
|         } | ||||
|         return result; | ||||
|       }); | ||||
|     }); | ||||
| 
 | ||||
|     // Apply response interceptors
 | ||||
|     this.interceptors.response.handlers.forEach(({ success, error }) => { | ||||
|       chain = chain.then(success, error); | ||||
|     }); | ||||
| 
 | ||||
|     return chain; | ||||
|   } | ||||
| 
 | ||||
|   // Helper methods for HTTP verbs
 | ||||
|   get(url, config) { | ||||
|     return this.request({ ...config, method: 'GET', url }); | ||||
|   } | ||||
|   delete(url, config) { | ||||
|     return this.request({ ...config, method: 'DELETE', url }); | ||||
|   } | ||||
|   head(url, config) { | ||||
|     return this.request({ ...config, method: 'HEAD', url }); | ||||
|   } | ||||
|   options(url, config) { | ||||
|     return this.request({ ...config, method: 'OPTIONS', url }); | ||||
|   } | ||||
|   post(url, data, config) { | ||||
|     return this.request({ ...config, method: 'POST', url, data }); | ||||
|   } | ||||
|   put(url, data, config) { | ||||
|     return this.request({ ...config, method: 'PUT', url, data }); | ||||
|   } | ||||
|   patch(url, data, config) { | ||||
|     return this.request({ ...config, method: 'PATCH', url, data }); | ||||
|   } | ||||
| 
 | ||||
|   // Utility to parse Fetch headers into an object
 | ||||
|   static _parseHeaders(headers) { | ||||
|     const result = {}; | ||||
|     for (const [key, value] of headers.entries()) { | ||||
|       result[key] = value; | ||||
|     } | ||||
|     return result; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| class Njet extends HTMLElement { | ||||
|     static _root = null | ||||
|     static showDialog = null | ||||
| 
 | ||||
|     get isRoot() { | ||||
|         return Njet._root === this | ||||
|     } | ||||
| 
 | ||||
|     get root() { | ||||
|         return Njet._root | ||||
|     } | ||||
| 
 | ||||
|     get rest() { | ||||
|         return Njet._root._rest | ||||
|     } | ||||
| 
 | ||||
|     _subscriptions = {} | ||||
|     _elements = [] | ||||
|     _rest = null | ||||
| 
 | ||||
|     match(args) { | ||||
|         return Object.entries(args).every(([key, value]) => this[key] === value); | ||||
|     } | ||||
| 
 | ||||
|     set(key, value) { | ||||
|         this.dataset[key] = value | ||||
|     } | ||||
| 
 | ||||
|     get(key, defaultValue) { | ||||
|         if (this.dataset[key]) { | ||||
|             return this.dataset[key] | ||||
|         } | ||||
|         if (defaultValue === undefined) { | ||||
|             return | ||||
|         } | ||||
|         this.dataset[key] = defaultValue | ||||
|         return this.dataset[key] | ||||
|     } | ||||
| 
 | ||||
|     showDialog(args){ | ||||
| 
 | ||||
|        // const dialog = this.createComponent('njet-dialog', args)
 | ||||
|    //     dialog.show()
 | ||||
|      //   return dialog()
 | ||||
|     } | ||||
| 
 | ||||
|     find(args) { | ||||
|         for (let element of this.root._elements) { | ||||
|             if (element.match(args)) { | ||||
|                 return element | ||||
|             } | ||||
|         } | ||||
|         return null | ||||
|     } | ||||
| 
 | ||||
|     findAll(args) { | ||||
|         return this.root._elements.filter(element => element.match(args)) | ||||
|     } | ||||
| 
 | ||||
|     subscribe(event, callback) { | ||||
|         if (!this.root._subscriptions[event]) { | ||||
|             this.root._subscriptions[event] = [] | ||||
|         } | ||||
|         this.root._subscriptions[event].push(callback) | ||||
|     } | ||||
| 
 | ||||
|     publish(event, data) { | ||||
|         if (this.root._subscriptions[event]) { | ||||
|             this.root._subscriptions[event].forEach(callback => callback(data)) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     static registerComponent(name, component) { | ||||
|         customElements.define(name, component); | ||||
|     } | ||||
| 
 | ||||
|     constructor(config = {}) { | ||||
|         super(); | ||||
|         if (!Njet._root) { | ||||
|             Njet._root = this | ||||
|             Njet._rest = new RestClient({ baseURL: config.baseURL || null }) | ||||
|         } | ||||
|         this.root._elements.push(this) | ||||
|         this.classList.add('njet'); | ||||
|         this.config = config; | ||||
|         this.render.call(this); | ||||
|         this.initProps(config); | ||||
|         if (typeof this.construct === 'function') | ||||
|             this.construct.call(this) | ||||
|     } | ||||
| 
 | ||||
|     initProps(config) { | ||||
|         const props = Object.keys(config) | ||||
|         props.forEach(prop => { | ||||
|             if (config[prop] !== undefined) { | ||||
|                 this[prop] = config[prop]; | ||||
|             } | ||||
|         }); | ||||
|         if (config.classes) { | ||||
|             this.classList.add(...config.classes); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     duplicate() { | ||||
|         const duplicatedConfig = { ...this.config }; | ||||
|         if (duplicatedConfig.items) { | ||||
|             duplicatedConfig.items = duplicatedConfig.items.map(item => { | ||||
|                 return typeof item.duplicate === 'function' ? item.duplicate() : item; | ||||
|             }); | ||||
|         } | ||||
|         return new this.constructor(duplicatedConfig); | ||||
|     } | ||||
| 
 | ||||
|     set width(val) { | ||||
|         this.style.width = typeof val === 'number' ? `${val}px` : val; | ||||
|     } | ||||
| 
 | ||||
|     get width() { return this.style.width; } | ||||
| 
 | ||||
|     set height(val) { | ||||
|         this.style.height = typeof val === 'number' ? `${val}px` : val; | ||||
|     } | ||||
| 
 | ||||
|     get height() { return this.style.height; } | ||||
| 
 | ||||
|     set left(val) { | ||||
|         this.style.position = 'absolute'; | ||||
|         this.style.left = typeof val === 'number' ? `${val}px` : val; | ||||
|     } | ||||
| 
 | ||||
|     get left() { return this.style.left; } | ||||
| 
 | ||||
|     set top(val) { | ||||
|         this.style.position = 'absolute'; | ||||
|         this.style.top = typeof val === 'number' ? `${val}px` : val; | ||||
|     } | ||||
| 
 | ||||
|     get top() { return this.style.top; } | ||||
| 
 | ||||
|     set opacity(val) { this.style.opacity = val; } | ||||
|     get opacity() { return this.style.opacity; } | ||||
| 
 | ||||
|     set disabled(val) { this.toggleAttribute('disabled', !!val); } | ||||
|     get disabled() { return this.hasAttribute('disabled'); } | ||||
| 
 | ||||
|     set visible(val) { this.style.display = val ? '' : 'none'; } | ||||
|     get visible() { return this.style.display !== 'none'; } | ||||
| 
 | ||||
|     render() {} | ||||
| } | ||||
| 
 | ||||
| class Component extends Njet {} | ||||
| 
 | ||||
| class NjetPanel extends Component { | ||||
|     render() { | ||||
|         this.innerHTML = ''; | ||||
|         const { title, items = [] } = this.config; | ||||
|         this.style.border = '1px solid #ccc'; | ||||
|         this.style.padding = '10px'; | ||||
|         if (title) { | ||||
|             const header = document.createElement('h3'); | ||||
|             header.textContent = title; | ||||
|             this.appendChild(header); | ||||
|         } | ||||
|         items.forEach(item => this.appendChild(item)); | ||||
|     } | ||||
| } | ||||
| Njet.registerComponent('njet-panel', NjetPanel); | ||||
| 
 | ||||
| class NjetButton extends Component { | ||||
|     render() { | ||||
|         this.classList.add('njet-button'); | ||||
|         this.innerHTML = ''; | ||||
|         const button = document.createElement('button'); | ||||
|         button.textContent = this.config.text || 'Button'; | ||||
|         if (typeof this.config.handler === 'function') { | ||||
|             button.addEventListener('click', (event) => this.config.handler.call(this)); | ||||
|         } | ||||
|         const observer = new MutationObserver(() => { | ||||
|             button.disabled = this.disabled; | ||||
|         }); | ||||
|         observer.observe(this, { attributes: true, attributeFilter: ['disabled'] }); | ||||
|         button.disabled = this.disabled; | ||||
|         this.appendChild(button); | ||||
|     } | ||||
| } | ||||
| Njet.registerComponent('njet-button', NjetButton); | ||||
| 
 | ||||
| class NjetDialog extends Component { | ||||
|     render() { | ||||
|         this.innerHTML = ''; | ||||
|         const { title, content, primaryButton, secondaryButton } = this.config; | ||||
|         this.classList.add('njet-dialog'); | ||||
|         this.style.position = 'fixed'; | ||||
|         this.style.top = '50%'; | ||||
|         this.style.left = '50%'; | ||||
|         this.style.transform = 'translate(-50%, -50%)'; | ||||
|         this.style.padding = '20px'; | ||||
|         this.style.border = '1px solid #444'; | ||||
|         this.style.backgroundColor = '#fff'; | ||||
|         this.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)'; | ||||
|         this.style.minWidth = '300px'; | ||||
|         if (title) { | ||||
|             const header = document.createElement('h2'); | ||||
|             header.textContent = title; | ||||
|             this.appendChild(header); | ||||
|         } | ||||
|         if (content) { | ||||
|             const body = document.createElement('div'); | ||||
|             body.innerHTML = content; | ||||
|             this.appendChild(body); | ||||
|         } | ||||
|         const buttonContainer = document.createElement('div'); | ||||
|         buttonContainer.style.marginTop = '20px'; | ||||
|         buttonContainer.style.display = 'flex'; | ||||
|         buttonContainer.style.justifyContent = 'flenjet-end'; | ||||
|         buttonContainer.style.gap = '10px'; | ||||
|         if (secondaryButton) { | ||||
|             const secondary = new NjetButton(secondaryButton); | ||||
|             buttonContainer.appendChild(secondary); | ||||
|         } | ||||
|         if (primaryButton) { | ||||
|             const primary = new NjetButton(primaryButton); | ||||
|             buttonContainer.appendChild(primary); | ||||
|         } | ||||
|         this.appendChild(buttonContainer); | ||||
|     } | ||||
| 
 | ||||
|     show(){ | ||||
|         document.body.appendChild(this) | ||||
|     } | ||||
| } | ||||
| Njet.registerComponent('njet-dialog', NjetDialog); | ||||
| 
 | ||||
| class NjetGrid extends Component { | ||||
|     render() { | ||||
|         this.classList.add('njet-grid'); | ||||
|         this.innerHTML = ''; | ||||
|         const table = document.createElement('table'); | ||||
|         table.style.width = '100%'; | ||||
|         table.style.borderCollapse = 'collapse'; | ||||
|         const data = this.config.data || []; | ||||
|         data.forEach(row => { | ||||
|             const tr = document.createElement('tr'); | ||||
|             Object.values(row).forEach(cell => { | ||||
|                 const td = document.createElement('td'); | ||||
|                 td.textContent = cell; | ||||
|                 td.style.border = '1px solid #ddd'; | ||||
|                 td.style.padding = '4px'; | ||||
|                 tr.appendChild(td); | ||||
|             }); | ||||
|             table.appendChild(tr); | ||||
|         }); | ||||
|         this.appendChild(table); | ||||
|     } | ||||
| } | ||||
| Njet.registerComponent('njet-grid', NjetGrid); | ||||
| /* | ||||
| const button = new NjetButton({ | ||||
|     classes: ['my-button'], | ||||
|     text: 'Shared', | ||||
|     tag: 'shared', | ||||
|     width: 120, | ||||
|     height: 30, | ||||
|     handler() { | ||||
|         this.root.findAll({ tag: 'shared' }).forEach(e => { | ||||
|             e.disabled = !e.disabled; | ||||
|         }); | ||||
|     } | ||||
| }); | ||||
| 
 | ||||
| const button2 = new NjetButton({ | ||||
|     classes: ['my-button'], | ||||
|     text: 'Single', | ||||
|     iValue: 0, | ||||
|     width: 120, | ||||
|     height: 30, | ||||
|     handler() { | ||||
|         this.iValue++; | ||||
|         const panel = this.closest('njet-panel'); | ||||
|         if (panel) { | ||||
|             const h3 = panel.querySelector('h3'); | ||||
|             if (h3) h3.textContent = `Click ${this.iValue}`; | ||||
|         } | ||||
|         this.publish("btn2Click", `Click ${this.iValue}`); | ||||
|     } | ||||
| }); | ||||
| 
 | ||||
| const grid = new NjetGrid({ | ||||
|     data: [ | ||||
|         { id: 1, name: 'John' }, | ||||
|         { id: 2, name: 'Jane' } | ||||
|     ], | ||||
|     width: '100%', | ||||
|     visible: true | ||||
| }); | ||||
| 
 | ||||
| const panel = new NjetPanel({ | ||||
|     title: 'My Panel', | ||||
|     items: [button, grid, button2], | ||||
|     left: 50, | ||||
|     top: 50, | ||||
|     construct: function () { | ||||
|         this.subscribe('btn2Click', (data) => { | ||||
|             this._title = data | ||||
|         }); | ||||
|     } | ||||
| }); | ||||
| 
 | ||||
| document.body.appendChild(panel); | ||||
| 
 | ||||
| const panelClone = panel.duplicate(); | ||||
| const panell = panelClone.duplicate(); | ||||
| panell.left = 120; | ||||
| panell.width = 300; | ||||
| panelClone.appendChild(panell); | ||||
| panelClone.left = 300; | ||||
| panelClone.top = 50; | ||||
| document.body.appendChild(panelClone); | ||||
| 
 | ||||
| const dialog = new NjetDialog({ | ||||
|     title: 'Confirm Action', | ||||
|     content: 'Are you sure you want to continue?', | ||||
|     primaryButton: { | ||||
|         text: 'Yes', | ||||
|         handler: function () { | ||||
|             alert('Confirmed'); | ||||
|             this.closest('njet-dialog').remove(); | ||||
|         } | ||||
|     }, | ||||
|     secondaryButton: { | ||||
|         text: 'Cancel', | ||||
|         handler: function () { | ||||
|             this.closest('njet-dialog').remove(); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| 
 | ||||
| document.body.appendChild(dialog); | ||||
| */ | ||||
| 
 | ||||
| class NjetComponent extends Component {} | ||||
|     const njet = Njet; | ||||
| njet.showDialog = function(args){ | ||||
|     const dialog = new NjetDialog(args)  | ||||
|     dialog.show() | ||||
|     return dialog  | ||||
| } | ||||
| 
 | ||||
| window.njet = njet  | ||||
| 
 | ||||
| export { Njet, NjetButton, NjetPanel, NjetDialog, NjetGrid, NjetComponent, njet}; | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user