Skip to content

Commit 3fe41a5

Browse files
committed
Enhance LivePreview: Add destroy logic and rename connection handlers #8072
1 parent d9e86fc commit 3fe41a5

4 files changed

Lines changed: 150 additions & 68 deletions

File tree

.github/.sync-metadata.json

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"lastSync": "2025-12-09T15:56:18.944Z",
3-
"releasesLastFetched": "2025-12-09T15:56:18.958Z",
2+
"lastSync": "2025-12-09T16:44:43.335Z",
3+
"releasesLastFetched": "2025-12-09T16:44:43.350Z",
44
"pushFailures": [],
55
"issues": {
66
"3789": {
@@ -12583,11 +12583,18 @@
1258312583
"contentHash": "e854143de697b5650cf2fd3cbf5b15e3771e2561b73d0246a4e626cfb469f735"
1258412584
},
1258512585
"8071": {
12586-
"state": "OPEN",
12586+
"state": "CLOSED",
1258712587
"path": "/Users/Shared/github/neomjs/neo/.github/ISSUE/issue-8071.md",
12588+
"closedAt": "2025-12-09T16:19:48Z",
12589+
"updatedAt": "2025-12-09T16:19:48Z",
12590+
"contentHash": "f8c9f43002761747f2d3c73205cb9ccbd0837769f32d382deb0645b30940c8df"
12591+
},
12592+
"8072": {
12593+
"state": "OPEN",
12594+
"path": "/Users/Shared/github/neomjs/neo/.github/ISSUE/issue-8072.md",
1258812595
"closedAt": null,
12589-
"updatedAt": "2025-12-09T15:56:04Z",
12590-
"contentHash": "ad2a8e2c2dec0b7660a03e2e5175c2718eafd308e766357c693c0bc372ba9889"
12596+
"updatedAt": "2025-12-09T16:44:28Z",
12597+
"contentHash": "5b140d26b13b62268f1af7cfb3c7038166c96c9d90f5f72a33994dc66cd93c4a"
1259112598
}
1259212599
},
1259312600
"releases": {

.github/ISSUE/issue-8071.md

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,25 @@
11
---
22
id: 8071
33
title: Refactor LivePreview to handle multi-window logic internally
4-
state: OPEN
4+
state: CLOSED
55
labels:
66
- enhancement
77
- ai
88
- refactoring
9-
assignees: []
9+
assignees:
10+
- tobiu
1011
createdAt: '2025-12-09T15:56:04Z'
11-
updatedAt: '2025-12-09T15:56:04Z'
12+
updatedAt: '2025-12-09T16:19:48Z'
1213
githubUrl: 'https://github.com/neomjs/neo/issues/8071'
1314
author: tobiu
14-
commentsCount: 0
15+
commentsCount: 1
1516
parentIssue: null
1617
subIssues: []
1718
subIssuesCompleted: 0
1819
subIssuesTotal: 0
1920
blockedBy: []
2021
blocking: []
22+
closedAt: '2025-12-09T16:19:48Z'
2123
---
2224
# Refactor LivePreview to handle multi-window logic internally
2325

@@ -33,9 +35,28 @@ This refactoring aims to decouple the `Neo.code.LivePreview` component from the
3335
**Note on Child Apps:**
3436
This refactor retains the requirement for the embedding application to provide the physical child app files (index.html, app.mjs) to ensure correct worker path resolution in production builds.
3537

38+
## Comments
39+
40+
### @tobiu - 2025-12-09 16:19
41+
42+
**Input from Gemini 3.0:**
43+
44+
> ✦ I have completed the refactoring to decouple `LivePreview` from `ViewportController`.
45+
>
46+
> **Changes Implemented:**
47+
> 1. **Logic Internalization:** `Neo.code.LivePreview` now self-manages its multi-window lifecycle. It directly subscribes to `Neo.currentWorker` `connect` and `disconnect` events.
48+
> 2. **Configuration:** Added a `windowUrl` config to `LivePreview` (defaults to `./childapps/preview/index.html`), allowing for flexible deployment paths.
49+
> 3. **Clean Separation:** Removed all `PortalPreview`-specific logic and the `onAppConnect` method from `Portal.view.ViewportController`.
50+
> 4. **Feature Guard:** The pop-out functionality and event listeners are now strictly conditional on `Neo.config.useSharedWorkers`.
51+
>
52+
> The component is now portable and reusable across different Neo.mjs applications without requiring a specific controller implementation. The embedding app is only responsible for providing the physical child app files (as referenced by `windowUrl`) to ensure correct worker path resolution.
53+
3654
## Activity Log
3755

3856
- 2025-12-09 @tobiu added the `enhancement` label
3957
- 2025-12-09 @tobiu added the `ai` label
4058
- 2025-12-09 @tobiu added the `refactoring` label
59+
- 2025-12-09 @tobiu assigned to @tobiu
60+
- 2025-12-09 @tobiu referenced in commit `d9e86fc` - "Refactor LivePreview to handle multi-window logic internally #8071"
61+
- 2025-12-09 @tobiu closed this issue
4162

.github/ISSUE/issue-8072.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
id: 8072
3+
title: 'Enhance LivePreview: Add destroy logic and rename connection handlers'
4+
state: OPEN
5+
labels:
6+
- enhancement
7+
- ai
8+
- refactoring
9+
assignees: []
10+
createdAt: '2025-12-09T16:44:28Z'
11+
updatedAt: '2025-12-09T16:44:28Z'
12+
githubUrl: 'https://github.com/neomjs/neo/issues/8072'
13+
author: tobiu
14+
commentsCount: 0
15+
parentIssue: null
16+
subIssues: []
17+
subIssuesCompleted: 0
18+
subIssuesTotal: 0
19+
blockedBy: []
20+
blocking: []
21+
---
22+
# Enhance LivePreview: Add destroy logic and rename connection handlers
23+
24+
This ticket covers two improvements for `Neo.code.LivePreview`:
25+
26+
1. **Clean up on destroy**:
27+
When a `LivePreview` instance is destroyed, it should ensure its associated pop-out window is closed.
28+
- Implement a `destroy()` method.
29+
- Use `Neo.Main.windowClose({names: [this.id]})` to close the specific window associated with this instance.
30+
31+
2. **Rename connection handlers**:
32+
To improve clarity regarding the data source (window vs app), rename the worker connection listeners:
33+
- `onAppConnect` -> `onWindowConnect`
34+
- `onAppDisconnect` -> `onWindowDisconnect`
35+
- The events receive `appName` and `windowId`, but since `appName` is not unique across tabs, `windowId` is the key identifier. Renaming reflects this focus.
36+
37+
## Activity Log
38+
39+
- 2025-12-09 @tobiu added the `enhancement` label
40+
- 2025-12-09 @tobiu added the `ai` label
41+
- 2025-12-09 @tobiu added the `refactoring` label
42+

src/code/LivePreview.mjs

Lines changed: 71 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -281,10 +281,22 @@ class LivePreview extends Container {
281281
Neo.Main.windowOpen({
282282
url : `${me.windowUrl}?id=${me.id}`,
283283
windowFeatures: `height=${height},left=${left},top=${top},width=${width}`,
284+
windowId : me.windowId,
284285
windowName : me.id
285286
})
286287
}
287288

289+
/**
290+
* @param {...*} args
291+
*/
292+
destroy(...args) {
293+
if (this.connectedWindowId) {
294+
Neo.Main.windowClose({names: [this.id], windowId: this.windowId})
295+
}
296+
297+
super.destroy(...args)
298+
}
299+
288300
/**
289301
* Executes the current source code.
290302
*
@@ -373,63 +385,6 @@ class LivePreview extends Container {
373385
me.disableRunSource = false;
374386
}
375387

376-
/**
377-
* @param {Object} data
378-
* @param {String} data.appName
379-
* @param {Number} data.windowId
380-
*/
381-
async onAppConnect(data) {
382-
let me = this,
383-
searchString = await Neo.Main.getByPath({path: 'location.search', windowId: data.windowId}),
384-
params = new URLSearchParams(searchString),
385-
id = params.get('id');
386-
387-
if (id === me.id) {
388-
me.connectedWindowId = data.windowId;
389-
390-
let app = Neo.apps[data.windowId],
391-
mainView = app.mainView,
392-
sourceContainer = me.getReference('preview'),
393-
{tabContainer} = me,
394-
sourceView = sourceContainer.removeAt(0, false);
395-
396-
me.previewContainer = mainView;
397-
mainView.add(sourceView);
398-
399-
tabContainer.activeIndex = 0; // switch to the source view
400-
401-
tabContainer.getTabAtIndex(1).disabled = true
402-
}
403-
}
404-
405-
/**
406-
* @param {Object} data
407-
* @param {String} data.appName
408-
* @param {Number} data.windowId
409-
*/
410-
async onAppDisconnect(data) {
411-
let me = this;
412-
413-
if (data.windowId === me.connectedWindowId) {
414-
let app = Neo.apps[data.windowId],
415-
mainView = app.mainView,
416-
sourceContainer = me.getReference('preview'),
417-
{tabContainer} = me,
418-
sourceView = mainView.removeAt(0, false);
419-
420-
me.previewContainer = null;
421-
sourceContainer.add(sourceView);
422-
423-
me.disableRunSource = true; // will get reset after the next activeIndex change (async)
424-
tabContainer.activeIndex = 1; // switch to the source view
425-
426-
me.getReference('popout-window-button').disabled = false;
427-
tabContainer.getTabAtIndex(1).disabled = false;
428-
429-
me.connectedWindowId = null
430-
}
431-
}
432-
433388
/**
434389
*
435390
*/
@@ -459,8 +414,8 @@ class LivePreview extends Container {
459414
});
460415

461416
Neo.currentWorker.on({
462-
connect : me.onAppConnect,
463-
disconnect: me.onAppDisconnect,
417+
connect : me.onWindowConnect,
418+
disconnect: me.onWindowDisconnect,
464419
scope : me
465420
})
466421
}
@@ -494,6 +449,63 @@ class LivePreview extends Container {
494449
}
495450
}
496451

452+
/**
453+
* @param {Object} data
454+
* @param {String} data.appName
455+
* @param {Number} data.windowId
456+
*/
457+
async onWindowConnect(data) {
458+
let me = this,
459+
searchString = await Neo.Main.getByPath({path: 'location.search', windowId: data.windowId}),
460+
params = new URLSearchParams(searchString),
461+
id = params.get('id');
462+
463+
if (id === me.id) {
464+
me.connectedWindowId = data.windowId;
465+
466+
let app = Neo.apps[data.windowId],
467+
mainView = app.mainView,
468+
sourceContainer = me.getReference('preview'),
469+
{tabContainer} = me,
470+
sourceView = sourceContainer.removeAt(0, false);
471+
472+
me.previewContainer = mainView;
473+
mainView.add(sourceView);
474+
475+
tabContainer.activeIndex = 0; // switch to the source view
476+
477+
tabContainer.getTabAtIndex(1).disabled = true
478+
}
479+
}
480+
481+
/**
482+
* @param {Object} data
483+
* @param {String} data.appName
484+
* @param {Number} data.windowId
485+
*/
486+
async onWindowDisconnect(data) {
487+
let me = this;
488+
489+
if (data.windowId === me.connectedWindowId) {
490+
let app = Neo.apps[data.windowId],
491+
mainView = app.mainView,
492+
sourceContainer = me.getReference('preview'),
493+
{tabContainer} = me,
494+
sourceView = mainView.removeAt(0, false);
495+
496+
me.previewContainer = null;
497+
sourceContainer.add(sourceView);
498+
499+
me.disableRunSource = true; // will get reset after the next activeIndex change (async)
500+
tabContainer.activeIndex = 1; // switch to the source view
501+
502+
me.getReference('popout-window-button').disabled = false;
503+
tabContainer.getTabAtIndex(1).disabled = false;
504+
505+
me.connectedWindowId = null
506+
}
507+
}
508+
497509
/**
498510
* @param {Object} data
499511
*/

0 commit comments

Comments
 (0)