-
Notifications
You must be signed in to change notification settings - Fork 10
Analyze plugin #147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release/v3
Are you sure you want to change the base?
Analyze plugin #147
Changes from all commits
642a059
37a78c9
87420e0
3e8db89
cf6a1d7
0cfe3ad
68e8652
e849e33
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
|
|
||
| Analyze plugin | ||
| ======================== | ||
|
|
||
| Purpose | ||
| ------- | ||
| Adds an "Analyze" tab to the AppBar with two actions: a "Run Recent →" anchor that opens a right-side recent-jobs panel, and "Create New App" which creates floating window with new app form. | ||
|
|
||
| Files | ||
| ----- | ||
| - `analyzeDropdown.mjs` - registers the tab and wires dropdown items. | ||
| - `newAppForm.mjs` - the floating form used by "Create New App". | ||
|
|
||
| How to use | ||
| ---------- | ||
| - Provide recent jobs by passing `params.recentJobs` or saving `recentJobs` via plugin options. | ||
| - Handle job clicks by implementing `onJobClick({ index, label })` on the plugin instance. | ||
| - Provide `params.onCreate` to receive form submission data from `NewAppForm`. | ||
|
|
||
| Implementation notes | ||
| -------------------- | ||
| - UI behaviors (menu, positioning, hover) are implemented in `SidePanel` (`setMenu`, `showNear`, `scheduleHide`, `cancelHide`) — reuse it for other flyouts. | ||
| - `SidePanel.hide()` currently removes the element; consider switching to `display:none` if you need faster show/hide cycles. | ||
|
|
||
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| { | ||
| "id": "analyze", | ||
| "name": "Analyze dropdown in Main Menu", | ||
| "author": "Filip Vrubel", | ||
| "version": "1.0.0", | ||
| "description": "Plugin for creating and running jobs", | ||
| "icon": null, | ||
| "includes" : ["newAppForm.mjs", "analyzeDropdown.mjs"], | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can include only main file, import/export is handled by browser automatically. This will preload the files no matter whether they are needed or not, browser will do it dynamically. Just FYI no need to change. |
||
| "permaLoad": true | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,194 @@ | ||
| import van from "../../ui/vanjs.mjs"; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. van is simplify available as a global, e.g. |
||
| import { BaseComponent } from "../../ui/classes/baseComponent.mjs"; | ||
| import { FloatingWindow } from "../../ui/classes/components/floatingWindow.mjs"; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This import isn’t correct. Unfortunatelly, our core system is still vanilla-js-first, so also the UI is compiled and available in the system through |
||
|
|
||
| const { div, input, textarea, span, button, label, h3 } = van.tags; | ||
|
|
||
| class NewAppForm extends BaseComponent { | ||
| constructor(options = {}) { | ||
| options = super(options).options; | ||
| this.id = options.id || 'new-app'; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not good ID. You are not alone. Prefer IDs that are more complex, at best, namespaced, e.g. by your plugin ID. |
||
| this.onSubmit = options.onSubmit || (() => {}); | ||
| this.values = options.values || {}; | ||
| this._el = null; | ||
| this._fields = {}; | ||
| this._floatingWindow = null; | ||
| } | ||
|
|
||
| _row(labelText, fieldNode) { | ||
| return div({ class: 'mb-2' }, | ||
| label({ class: 'block mb-1 text-xs font-medium' }, labelText), | ||
| fieldNode | ||
| ); | ||
| } | ||
|
|
||
| create() { | ||
| if (this._el) return this._el; | ||
|
|
||
| // use explicit inline sizing so styles apply even if utility classes are missing | ||
| const smallInputStyle = 'height:30px;padding:4px 6px;font-size:12px;line-height:1.1;box-sizing:border-box;'; | ||
| const smallTextareaBase = 'padding:6px 6px;font-size:12px;line-height:1.2;box-sizing:border-box;resize:vertical;'; | ||
| const schemaEl = input({ type: 'text', id: this.id + '-schema', class: 'input w-full', style: smallInputStyle, value: this.values.schema || '' }); | ||
| const nameEl = input({ type: 'text', id: this.id + '-name', class: 'input w-full', style: smallInputStyle, value: this.values.name || '' }); | ||
| const nsEl = input({ type: 'text', id: this.id + '-namespace', class: 'input w-full', style: smallInputStyle, value: this.values.namespace || '' }); | ||
| const descEl = textarea({ id: this.id + '-description', class: 'textarea w-full', style: smallTextareaBase + 'height:64px;', rows: 4 }, this.values.description || ''); | ||
| const inputsEl = textarea({ id: this.id + '-inputs', class: 'textarea w-full', style: smallTextareaBase + 'height:48px;', rows: 3 }, this.values.inputs || ''); | ||
| const outputsEl = textarea({ id: this.id + '-outputs', class: 'textarea w-full', style: smallTextareaBase + 'height:48px;', rows: 3 }, this.values.outputs || ''); | ||
| const jobEl = input({ type: 'text', id: this.id + '-joburl', class: 'input w-full', style: smallInputStyle, value: this.values.jobUrl || '' }); | ||
|
|
||
| const btnEdit = button({ class: 'btn btn-secondary btn-sm mr-2', type: 'button', onclick: (ev) => this._onEdit() }, 'Edit EAD'); | ||
| const btnCreate = button({ class: 'btn btn-primary btn-sm', type: 'button', onclick: (ev) => this._onCreate() }, 'Create'); | ||
|
|
||
| // remove inner title and close button to rely on the FloatingWindow header | ||
| // increase top padding so the FloatingWindow title has breathing room | ||
| const form = div({ class: 'p-4 bg-base-200 border border-base-300 rounded-md max-w-full relative', style: 'max-width:420px;width:100%;' }, | ||
| this._row('Schema:', schemaEl), | ||
| this._row('Name:', nameEl), | ||
| this._row('Namespace:', nsEl), | ||
| this._row('Description:', descEl), | ||
| this._row('Inputs:', inputsEl), | ||
| this._row('Outputs:', outputsEl), | ||
| this._row('Job URL:', jobEl), | ||
| div({ class: 'mt-4 flex gap-2 justify-end' }, btnEdit, btnCreate) | ||
| ); | ||
|
|
||
| this._fields = { | ||
| schema: schemaEl, | ||
| name: nameEl, | ||
| namespace: nsEl, | ||
| description: descEl, | ||
| inputs: inputsEl, | ||
| outputs: outputsEl, | ||
| jobUrl: jobEl | ||
| }; | ||
|
|
||
| // make the form fill available height inside a FloatingWindow and layout vertically | ||
| try { | ||
| this._el = form; | ||
| // allow flex layout to let textareas expand/shrink with window | ||
| this._el.style.display = 'flex'; | ||
| this._el.style.flexDirection = 'column'; | ||
| this._el.style.height = '100%'; | ||
|
|
||
| // ensure textareas grow and shrink with the container (min-height:0 prevents overflow) | ||
| const makeFlexTA = (el) => { | ||
| if (!el) return; | ||
| el.style.flex = '1 1 auto'; | ||
| el.style.minHeight = '0'; | ||
| // keep existing padding/box sizing | ||
| }; | ||
| makeFlexTA(this._fields.description); | ||
| makeFlexTA(this._fields.inputs); | ||
| makeFlexTA(this._fields.outputs); | ||
|
|
||
| // push buttons to bottom | ||
| const btnContainer = this._el.querySelector('.mt-4'); | ||
| if (btnContainer) btnContainer.style.marginTop = 'auto'; | ||
| } catch (e) { | ||
| // non-fatal: fall back to previous behavior | ||
| this._el = form; | ||
| } | ||
|
|
||
| return this._el; | ||
| } | ||
|
|
||
| /** | ||
| * Close helper: prefer closing a floating window if we opened one; | ||
| * otherwise close the parent Dialog via Dialogs.closeWindow if present, | ||
| * fallback to removing the element from DOM (legacy behavior). | ||
| */ | ||
| _close() { | ||
| try { | ||
| if (this._floatingWindow) { | ||
| try { this._floatingWindow.close(); } catch(_) {} | ||
| this._floatingWindow = null; | ||
| return; | ||
| } | ||
| // if inside a Dialog created via Dialogs.showCustom, find the dialog root | ||
| let root = this._el && this._el.closest ? this._el.closest('[data-dialog="true"]') : null; | ||
| if (root && root.id && window.USER_INTERFACE && USER_INTERFACE.Dialogs && typeof USER_INTERFACE.Dialogs.closeWindow === 'function') { | ||
| try { USER_INTERFACE.Dialogs.closeWindow(root.id); return; } catch (_) {} | ||
| } | ||
| // fallback: traditional removal of the attached wrapper | ||
| const el = this._el; | ||
| if (!el) return; | ||
| const wrapper = el.parentNode; | ||
| if (wrapper && wrapper.parentNode) { | ||
| wrapper.parentNode.removeChild(wrapper); | ||
| } else if (el.parentNode) { | ||
| el.parentNode.removeChild(el); | ||
| } | ||
| } catch (e) { console.error('NewAppForm _close error', e); } | ||
| } | ||
|
|
||
| _serialize() { | ||
| const f = this._fields; | ||
| const getVal = (el) => (el && el.value !== undefined) ? el.value : (el && el.textContent) || ''; | ||
| return { | ||
| schema: getVal(f.schema), | ||
| name: getVal(f.name), | ||
| namespace: getVal(f.namespace), | ||
| description: getVal(f.description), | ||
| inputs: getVal(f.inputs), | ||
| outputs: getVal(f.outputs), | ||
| jobUrl: getVal(f.jobUrl), | ||
| }; | ||
| } | ||
|
|
||
| _onEdit() { | ||
| } | ||
|
Comment on lines
+138
to
+139
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The button is visible but does nothing. Consider hiding/disable until implemented or wire a callback. Happy to help implement the handler. 🤖 Prompt for AI Agents
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's good to leave todo comments where there is expected a work to be done. Also same issue as above - we should not really realy on EAD - its EMPAIA thing. We should support 'some config' in a more generic way, but as before-maybe let's first get EMPAIA working, and then continue generalization of the logics. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
_onEdit() {
// TODO: Implement EAD editor integration (EMPAIA-specific, will be generalized later)
}This approach is consistent with the current strategy of getting EMPAIA working first before adding abstraction layers for multiple app sources. The TODO makes it clear this is intentional technical debt that will be addressed during future refactoring. 🧠 Learnings used |
||
|
|
||
| _onCreate() { | ||
| const data = this._serialize(); | ||
| try { | ||
| const r = this.onSubmit(data); | ||
| if (r !== false) { | ||
| try { if (this._floatingWindow) { this._floatingWindow.close(); this._floatingWindow = null; } } catch(_) {} | ||
| } | ||
|
Comment on lines
+141
to
+147
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Close non‑floating hosts after successful submit. For Dialog/embedded paths, the form stays open. Calling 🛠️ Suggested fix- const r = this.onSubmit(data);
- if (r !== false) {
- try { if (this._floatingWindow) { this._floatingWindow.close(); this._floatingWindow = null; } } catch(_) {}
- }
+ const r = this.onSubmit(data);
+ if (r !== false) {
+ this._close();
+ }🤖 Prompt for AI Agents
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I dont understand, it closes the window if it is defined - its just a sanity check. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The current code in Current code: if (r !== false) {
try { if (this._floatingWindow) { this._floatingWindow.close(); this._floatingWindow = null; } } catch(_) {}
}This only closes the form when it's shown via But the form can be used in three different ways:
Looking at your
CodeRabbit's suggestion is to simply call So it's not just a sanity check—it's about ensuring the form closes properly regardless of how it was opened. Does this clarify the suggestion? 🧠 Learnings used |
||
| } catch (e) { | ||
| console.error('NewAppForm onSubmit error', e); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Show this form inside a FloatingWindow. Returns the FloatingWindow instance. | ||
| * Options may include width/height/title. | ||
| */ | ||
| showFloating(opts = {}) { | ||
| try { | ||
| if (this._floatingWindow) return this._floatingWindow; | ||
| const id = this.id + '-window'; | ||
| // compute centered start position when possible | ||
| const width = opts.width || 420; | ||
| // increase default height so the form fits comfortably; still allows scrolling | ||
| const height = opts.height || 520; | ||
| const startLeft = (typeof window !== 'undefined') ? Math.max(8, Math.round((window.innerWidth - width) / 2)) : (opts.startLeft || 64); | ||
| const startTop = (typeof window !== 'undefined') ? Math.max(8, Math.round((window.innerHeight - height) / 2)) : (opts.startTop || 64); | ||
| const w = new FloatingWindow({ id, title: opts.title || 'New App', width, height, startLeft, startTop, onClose: () => { this._floatingWindow = null; } }, ); | ||
| // attach window to body so it is visible | ||
| w.attachTo(document.body); | ||
| // wrap the form in a scrollable card-body so FloatingWindow keeps expected layout | ||
| const wrapper = document.createElement('div'); | ||
| wrapper.className = 'card-body p-3 gap-2 overflow-auto'; | ||
| wrapper.style.height = '100%'; | ||
| wrapper.appendChild(this.create()); | ||
| // set the body to our wrapper node | ||
| w.setBody(wrapper); | ||
| this._floatingWindow = w; | ||
| return w; | ||
| } catch (e) { console.error('NewAppForm showFloating error', e); } | ||
| return null; | ||
| } | ||
|
|
||
| attachTo(parent) { | ||
| const target = (typeof parent === 'string') ? document.getElementById(parent) : parent; | ||
| if (!target) throw new Error('attachTo: parent not found'); | ||
| target.appendChild(this.create()); | ||
| } | ||
|
|
||
| getValues() { | ||
| return this._serialize(); | ||
| } | ||
| } | ||
|
|
||
| export { NewAppForm }; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -96,37 +96,67 @@ export class BaseComponent { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param {*} element - The element to attach the component to | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @return {BaseComponent} builder pattern | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| attachTo(element) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.refreshClassState(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.refreshPropertiesState(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (element instanceof BaseComponent) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Accept true BaseComponent instances and component-like objects | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const isComponentLike = (el) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return !!el && (el instanceof BaseComponent || (typeof el === "object" && typeof el.create === "function" && typeof el.id === "string")); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isComponentLike(element)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const mount = document.getElementById(element.id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (mount === null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| element._children.push(this); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mount.append(this.create()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+106
to
118
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, locate the baseComponent.mjs file
find . -name "baseComponent.mjs" -type fRepository: RationAI/xopat Length of output: 172 🏁 Script executed: # Read the relevant section with context
cat -n ui/classes/baseComponent.mjs | sed -n '90,130p'Repository: RationAI/xopat Length of output: 1946 🏁 Script executed: # Search for component-like object definitions and usage patterns
rg -A 5 "component-like|isComponentLike" ui/classes/baseComponent.mjsRepository: RationAI/xopat Length of output: 584 🏁 Script executed: # Check for _children property usage and initialization
rg "_children" ui/classes/baseComponent.mjsRepository: RationAI/xopat Length of output: 767 🏁 Script executed: # Look for test files related to attachTo
find . -name "*.test.*" -o -name "*.spec.*" | head -20Repository: RationAI/xopat Length of output: 40 🏁 Script executed: # Search for actual component-like object definitions (objects with create and id properties)
rg "create.*function|\.create\s*:" . --type-list | grep -v node_modules | head -20Repository: RationAI/xopat Length of output: 447 🏁 Script executed: # Look for prependedTo method to see if similar pattern exists
rg -B 5 -A 10 "prependedTo" ui/classes/baseComponent.mjsRepository: RationAI/xopat Length of output: 595 🏁 Script executed: # Search for test files that test attachTo with component-like objects
find . -type f \( -name "*.test.js" -o -name "*.spec.js" -o -name "test*.js" \) | xargs grep -l "attachTo" 2>/dev/null | head -5Repository: RationAI/xopat Length of output: 40 🏁 Script executed: # Look for other instances where _children is accessed on non-BaseComponent instances
rg "element\._children" ui/classes/baseComponent.mjs -B 2 -A 2Repository: RationAI/xopat Length of output: 1138 🏁 Script executed: # Check if prependedTo has similar _children.unshift issue with component-like objects
cat -n ui/classes/baseComponent.mjs | sed -n '135,160p'Repository: RationAI/xopat Length of output: 1356 🏁 Script executed: # Verify component-like objects are actually being used and passed around
rg "create\s*[:=].*function|create\s*\(" . --type js -B 2 -A 2 | head -40Repository: RationAI/xopat Length of output: 4613 Guard Both methods access The 🛠️ Suggested fix if (isComponentLike(element)) {
const mount = document.getElementById(element.id);
if (mount === null) {
- element._children.push(this);
+ if (element instanceof BaseComponent && Array.isArray(element._children)) {
+ element._children.push(this);
+ } else {
+ console.error("Failed to attach: component-like target has no mount and no _children queue", element);
+ }
} else {
mount.append(this.create());
}Apply the same guard to 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, why is this check added? I don't see a scenario where it would be needed, we usually require inheritance from a BaseComponent. Maybe this would fail if we have longer inheritance chain... so checking on props explicitly might be benefical, but then we need to check There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: Length of output: 11912 🏁 Script executed: Length of output: 72226 🏁 Script executed: Length of output: 65999
No usage of component-like plain objects found:
The
My recommendation: Remove 🧠 Learnings used |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const mount = typeof element === "string" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Resolve mount target from id/string or direct reference | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let mount = typeof element === "string" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ? document.getElementById(element) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| : element; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // If we got a jQuery/Cash wrapper, unwrap to the first DOM node | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In general the API was not designed to work with a jquery - when you wanted to use it, you had to supply a DOM node. Is there a need to add the jquery support? I would like to keep things simple where possible. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // (supports libraries exposing .jquery flag or .get/.[0]) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (mount && (mount.jquery || typeof mount.get === "function" || Array.isArray(mount))) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const candidate = typeof mount.get === "function" ? mount.get(0) : (Array.isArray(mount) ? mount[0] : mount[0]); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (candidate) mount = candidate; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!mount) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error(`Element ${element} not found`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| van.add(element, this.create()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| van.add(element, this.create()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (_) { /* noop: element may be invalid for van.add */ } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (typeof mount.append === "function") { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mount.append(this.create()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (mount.nodeType || mount instanceof Node) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Fallback for very old environments where append may be missing | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const created = this.create(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see a lot of fixes for attachTo method, but there are other attaching logics elsewhere. This is the CORE. If you do immature changes, you change EVERY component - in case you NEED these stability improvements, make sure all of the API is CONSISTENT. what about |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (mount.appendChild) mount.appendChild(created); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { mount.innerHTML += created.outerHTML || String(created); } catch (_) {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Last resort: try jQuery-style append if available or log a clearer error | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { mount.append(this.create()); } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| catch (e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error("Failed to attach component: unsupported mount target", mount); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error(e); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return this; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param {*} element - The element to prepend the component to | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @return {BaseComponent} builder pattern | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| prependedTo(element) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.refreshClassState(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -151,6 +181,7 @@ export class BaseComponent { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| mount.prepend(this.create()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return this; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -267,6 +298,22 @@ export class BaseComponent { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.classState.val = Object.values(this.classMap).join(" "); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Toggle the class of the component | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param {string} key | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param {string} value | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param {boolean} on if true, set class | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| toggleClass(key, value, on=true) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.classMap[key] = on ? value : ""; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.classState.val = Object.values(this.classMap).join(" "); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Set attribute property to the element | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param {string} key attribute name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param {string} value | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setExtraProperty(key, value) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.propertiesMap[key] = value; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let stateMap = this.propertiesStateMap[key]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -409,3 +456,18 @@ export class BaseComponent { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.refreshPropertiesState(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @typedef {BaseUIOptions} SelectableUIOptions | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @property {string} [itemID] - The selection ID | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| export class BaseSelectableComponent extends BaseComponent { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| constructor(options, ...args) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| options = super(options, ...args); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.itemID = options.itemID || this.id; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+465
to
+468
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find . -name "baseComponent.mjs" -type fRepository: RationAI/xopat Length of output: 88 🏁 Script executed: # First, let's locate and read the file
fd baseComponent.mjsRepository: RationAI/xopat Length of output: 86 🏁 Script executed: # Check the structure around the target lines
cat -n ui/classes/baseComponent.mjs | sed -n '460,475p'Repository: RationAI/xopat Length of output: 562 🏁 Script executed: # Let's see more context - the class definition and parent
cat -n ui/classes/baseComponent.mjs | head -100Repository: RationAI/xopat Length of output: 4368 🏁 Script executed: # Search for BaseSelectableComponent usage and instantiation
rg "BaseSelectableComponent" -A 2 -B 2Repository: RationAI/xopat Length of output: 396 🏁 Script executed: # Also check if there are any tests that might show the actual behavior
fd -e test.js -o -e test.mjs -o -e spec.js -o -e spec.mjs | head -20Repository: RationAI/xopat Length of output: 174 🏁 Script executed: # Examine the BaseComponent constructor return statement
cat -n ui/classes/baseComponent.mjs | sed -n '50,96p' | grep -E "return|constructor"Repository: RationAI/xopat Length of output: 104 🏁 Script executed: # Final check - search for any explicit return in BaseComponent
rg "return" ui/classes/baseComponent.mjs | head -5Repository: RationAI/xopat Length of output: 316 Fix constructor to properly access options after super call. In the 🐛 Fix constructor option handling- constructor(options, ...args) {
- options = super(options, ...args);
- this.itemID = options.itemID || this.id;
- }
+ constructor(options, ...args) {
+ super(options, ...args);
+ const opts = this.options || {};
+ this.itemID = opts.itemID || this.id;
+ }🤖 Prompt for AI Agents
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This fix of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: Length of output: 47 🏁 Script executed: Length of output: 295 🏁 Script executed: Length of output: 1171
constructor(options, ...args) {
options = super(options, ...args).options;
this.itemID = options.itemID || this.id;
}This was introduced in commit The 🧬 Code Graph Analysis Results[ui/classes/baseComponent.mjs, lines 260-264] export class BaseSelectableComponent extends BaseComponent { } 🧠 Learnings used |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setSelected(itemID) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new Error("Component must override setSelected method"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documentation? Awesome!