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