feat(cutover): every entry point opens the workspace; legacy pages stay reachable
The top-bar PM button, the nodes' Web UI buttons and /prompt_manager/web now open the workspace Library. Overview's cards open their native pages. The legacy pages stay at their URLs, with no route changed. Classic Web UI no longer navigates the canvas away when a popup is blocked; it shows a notification, as the focused node does. Closes #525
This commit is contained in:
@@ -251,9 +251,9 @@ match Classic and Focused.
|
||||
- **PromptManagerText**: Look for "Prompt Manager Text" in the PromptManager/Text category - outputs STRING for any text node
|
||||
|
||||
5. **Access the web interface**:
|
||||
- **Admin Dashboard**: `http://localhost:8188/prompt_manager/admin` - Full management interface
|
||||
- **Metadata Viewer**: `http://localhost:8188/prompt_manager/metadata.html` - Standalone PNG analysis tool
|
||||
- **Simple Interface**: `http://localhost:8188/prompt_manager/` - Basic prompt browser
|
||||
- **Workspace**: click **PM** in ComfyUI's top bar or **Web UI** on a node, or open `http://localhost:8188/prompt_manager/workspace`
|
||||
- **Legacy pages** still work at their URLs, and the workspace doesn't link to them:
|
||||
`/prompt_manager/admin` (dashboard), `/prompt_manager/admin#/tags` (tags), `/prompt_manager/gallery` (gallery), `/prompt_manager/gallery.html` (metadata viewer)
|
||||
|
||||
### Prerequisites
|
||||
|
||||
@@ -324,9 +324,10 @@ The comprehensive gallery system provides complete access to all your ComfyUI ou
|
||||
|
||||
#### **📁 Accessing the Gallery**
|
||||
|
||||
1. **Open the admin interface** at `http://localhost:8188/prompt_manager/admin`
|
||||
2. **Click the "🖼️ Gallery" button** to open the output-wide gallery
|
||||
3. **Browse all content** from your ComfyUI output directory
|
||||
1. **Open the workspace's Images page** at `http://localhost:8188/prompt_manager/workspace#/images`
|
||||
2. **Browse all content** from your ComfyUI output directory
|
||||
|
||||
The legacy gallery stays at `http://localhost:8188/prompt_manager/gallery`.
|
||||
|
||||
#### **🎬 Video & Image Support**
|
||||
|
||||
|
||||
+16
-6
@@ -2641,8 +2641,8 @@ applied), D35 (singular captions), D36 (sorted categories), D40 (the continuatio
|
||||
the grid) and D43 (sorted chips, checked against the concept with its chips sorted).
|
||||
D44 needs no mapping: the comparison runs with no Refine filter active, so the button
|
||||
reads "Filters" as the concept's does. Nothing else follows the grid: the legacy
|
||||
"Current prompt library" link is removed from the shelf; Overview's legacy card ("Open
|
||||
existing prompt library") and the sidebar still link `/prompt_manager/admin`
|
||||
"Current prompt library" link is removed from the shelf; the legacy Library stays at
|
||||
`/prompt_manager/admin` by direct URL, with no workspace link (#525)
|
||||
(`tests/browser/workspace.spec.mjs`). `library comparison detects
|
||||
mutations` injects `.card-footer { padding: 2px }` and removes `.card-meta .chip.red`.
|
||||
|
||||
@@ -3109,8 +3109,8 @@ npm run --prefix tools/vendor test:workflows -- history-concept.spec.mjs \
|
||||
### Inspect a file page and recorded deviations (#249 local Inspect, PR L2)
|
||||
|
||||
`#/inspect` ("Inspect a file") is a native workspace route; it no longer hands off to
|
||||
the legacy metadata page, which stays reachable from Overview's "Open existing metadata
|
||||
inspector" card (`/prompt_manager/gallery.html`). The page is the concept's `#metadata`
|
||||
the legacy metadata page, which stays at its URL
|
||||
(`/prompt_manager/gallery.html`) with no workspace link (#525). The page is the concept's `#metadata`
|
||||
(app.js `renderMetadata` a740–748, selection a1715–1729; style.css 709–722, 1180–1194,
|
||||
1543–1545, 1559–1564) in the concept's own classes (`.surface`, `.file-drop`,
|
||||
`.spaced-20`, `.local-file-preview`, all from the synced `concept.css`), with no
|
||||
@@ -7021,8 +7021,8 @@ Deploying this backend needs a ComfyUI restart (Python and route changes).
|
||||
## Native vocabulary UI (#236)
|
||||
|
||||
`#/tags` ("Tags & categories", page title "Tags") is a native workspace route; it no
|
||||
longer hands off to the legacy tag manager. That page stays reachable from Overview's
|
||||
"Open existing tag manager" card for its own page; tag search and sort, the untagged
|
||||
longer hands off to the legacy tag manager. That page stays at
|
||||
`/prompt_manager/admin#/tags` with no workspace link (#525); tag search and sort, the untagged
|
||||
entry (D149) and multi-tag OR (the Library's Tag match, D150) moved to the workspace
|
||||
(#444); bulk prompt delete stays on the legacy admin page (bulk tags: the Prompt
|
||||
library's selection bar, D151). A prompt's category is changed in the Prompt library
|
||||
@@ -7144,3 +7144,13 @@ until acceptance. Not compared: hover states and the dialog focus ring (the page
|
||||
keyboard ring and link ring offsets are asserted). No coverage floor was lowered;
|
||||
`vocabulary-state.mjs`, `vocabulary.mjs` and `vocabulary-dialog.mjs` have additive
|
||||
floors in `frontend-coverage-baseline.json`.
|
||||
|
||||
## Cutover entry points (#525)
|
||||
|
||||
The top-bar PM button, both Web UI buttons and `/prompt_manager/web` open
|
||||
`/prompt_manager/workspace#/library` (`web/js/workspace-entry.js`; `web/index.html` keeps its
|
||||
own copy, pinned by `tests/js/workspace-entry.test.js`). Overview's cards open native hashes.
|
||||
The legacy pages (`/prompt_manager/admin`, `admin#/tags`, `gallery`, `gallery.html`) stay at
|
||||
their URLs. Only the workspace's `<noscript>` links one, and admin's Metadata and Gallery buttons
|
||||
still open their legacy pages (pinned by `legacy-system-links.spec.mjs`). The PM button has no
|
||||
page-fixture test; it is proven on the managed host (`tests/comfyui/prompt-manager.spec.mjs`).
|
||||
|
||||
@@ -7,7 +7,7 @@ test.beforeEach(async ({page, browserName}) => {
|
||||
page.classicErrors = [];
|
||||
page.on('pageerror', error => page.classicErrors.push(error.message));
|
||||
await page.route('**/scripts/app.js', route => route.fulfill({contentType:'text/javascript', body:'export const app = window.fixtureApp;'}));
|
||||
await page.route('**/scripts/api.js', route => route.fulfill({contentType:'text/javascript', body:'export const api = {fileURL: path => path, queuePrompt: async (...args) => args};'}));
|
||||
await page.route('**/scripts/api.js', route => route.fulfill({contentType:'text/javascript', body:'export const api = {fileURL: path => (window.fixtureApiBase ?? "") + path, queuePrompt: async (...args) => args};'}));
|
||||
await page.route('**/extensions/promptmanager/prompt_manager.js', route => route.fulfill({contentType:'text/javascript', body:readFileSync(new URL('../../web/comfy/prompt_manager.js', import.meta.url),'utf8')}));
|
||||
await page.goto('/prompt_manager/test');
|
||||
await page.evaluate(() => {
|
||||
@@ -64,6 +64,28 @@ test('Classic keeps immediate selection, search query and metadata serialization
|
||||
await expect(page.getByRole('button',{name:'🌐 Web UI',exact:true})).toBeVisible();
|
||||
});
|
||||
|
||||
test('Classic Web UI opens the workspace Library in the saved display mode, through the mount prefix (#525)',async({page})=>{
|
||||
await page.evaluate(()=>{window.fixtureApiBase='/mounted';window.open=(...args)=>{window.openArgs=args;return {};};});
|
||||
const webUi=page.getByRole('button',{name:'🌐 Web UI',exact:true});
|
||||
for (const [mode,args] of [
|
||||
['newtab',['/mounted/prompt_manager/workspace#/library','_blank']],
|
||||
['popup',['/mounted/prompt_manager/workspace#/library','_blank','width=1200,height=800,scrollbars=yes,resizable=yes']],
|
||||
]) {
|
||||
await page.evaluate(value=>{node.properties.webuiDisplayMode=value;},mode);
|
||||
await webUi.click();
|
||||
expect(await page.evaluate(()=>window.openArgs)).toEqual(args);
|
||||
}
|
||||
});
|
||||
|
||||
test('Classic Web UI reports a blocked popup and keeps the canvas open (#525)',async({page})=>{
|
||||
const before=page.url();
|
||||
await page.evaluate(()=>{window.open=()=>null;});
|
||||
await page.getByRole('button',{name:'🌐 Web UI',exact:true}).click();
|
||||
await expect(page.getByText('Allow popups to open Web UI, then try again.')).toBeVisible();
|
||||
expect(page.url()).toBe(before);
|
||||
await expect(page.locator('.pm-classic')).toBeVisible();
|
||||
});
|
||||
|
||||
test('Classic detaches native text synchronization on removal',async({page})=>{
|
||||
// The page installs the text sync (serializeValue and its listeners) in a 50 ms timer; wait for it, not for time.
|
||||
await expect.poll(()=>page.evaluate(()=>typeof node.widgets.find(item=>item.name==='text').serializeValue)).toBe('function');
|
||||
|
||||
@@ -485,7 +485,7 @@ test(`${variant} respects the existing Web UI display preference`,async({page})=
|
||||
});
|
||||
await page.evaluate(()=>node._focusedSettingsReady);
|
||||
await page.getByRole('button',{name:'Web UI ↗',exact:true}).click();
|
||||
expect(await page.evaluate(()=>window.openArgs)).toEqual(['/prompt_manager/web','_blank']);
|
||||
expect(await page.evaluate(()=>window.openArgs)).toEqual(['/prompt_manager/workspace#/library','_blank']);
|
||||
});
|
||||
|
||||
test(`${variant} reports blocked Web UI popups without leaving the workflow`,async({page})=>{
|
||||
|
||||
@@ -11,12 +11,12 @@ const sidebarLink = (page, label) => page.locator('aside.sidebar nav').getByRole
|
||||
const sidebarLinks = page => page.locator('aside.sidebar nav a');
|
||||
const routes = [
|
||||
['overview', 'Overview', null],
|
||||
// D29: Images no longer links the legacy gallery; Overview's tool cards still do.
|
||||
// D29: Images no longer links the legacy gallery.
|
||||
['images', 'Images', null],
|
||||
// #249 E3: the shelf no longer links the legacy Library; Overview's tool card still does.
|
||||
// #249 E3: the shelf no longer links the legacy Library.
|
||||
['library', 'Prompt library', null],
|
||||
['tags', 'Tags & categories', null],
|
||||
// #249 local Inspect: the native Inspect a file page; Overview's tool card still links the legacy metadata page.
|
||||
// #249 local Inspect: the native Inspect a file page.
|
||||
['inspect', 'Inspect a file', null],
|
||||
// #249 System S2: native System; every section native since S12 (D94 retired, asserted below).
|
||||
['system', 'System', null],
|
||||
@@ -93,20 +93,41 @@ test('all shell destinations deep-link, reload and expose working existing appli
|
||||
await page.reload();
|
||||
await expect(main.getByRole('heading', { level: 1, name: title, exact: true })).toBeVisible();
|
||||
}
|
||||
// D29 and #249 E3: the legacy gallery and Library are reached from Overview's tool cards, not from their pages.
|
||||
// #525: Overview's cards open the native pages, and no workspace page links a legacy one.
|
||||
for (const [action, hash, title] of [
|
||||
['Open Images', '#/images', 'Images'],
|
||||
['Open Prompt library', '#/library', 'Prompt library'],
|
||||
['Open Tags & categories', '#/tags', 'Tags & categories'],
|
||||
['Open Inspect a file', '#/inspect', 'Inspect a file'],
|
||||
['Open System', '#/system/overview', 'System'],
|
||||
]) {
|
||||
await page.goto(`${workspace}#/overview`);
|
||||
const main = page.locator('#workspace-main');
|
||||
const card = main.getByRole('link', { name: action, exact: true });
|
||||
await expect(card).toHaveAttribute('href', hash);
|
||||
await card.click();
|
||||
await expect(page).toHaveURL(new RegExp(`/prompt_manager/workspace${hash}$`));
|
||||
await expect(main.getByRole('heading', { level: 1, name: title, exact: true })).toBeVisible();
|
||||
}
|
||||
await page.goto(`${workspace}#/overview`);
|
||||
const library = page.locator('#workspace-main').getByRole('link', { name: 'Open existing prompt library', exact: true });
|
||||
await expect(library).toHaveAttribute('href', '/prompt_manager/admin');
|
||||
await expect(await request.get('/prompt_manager/admin')).toBeOK();
|
||||
// The legacy metadata page stays reachable from Overview beside the native Inspect page.
|
||||
const metadata = page.locator('#workspace-main').getByRole('link', { name: 'Open existing metadata inspector', exact: true });
|
||||
await expect(metadata).toHaveAttribute('href', '/prompt_manager/gallery.html');
|
||||
await expect(await request.get('/prompt_manager/gallery.html')).toBeOK();
|
||||
const gallery = page.locator('#workspace-main').getByRole('link', { name: 'Open existing image gallery', exact: true });
|
||||
await expect(gallery).toHaveAttribute('href', '/prompt_manager/gallery');
|
||||
await expect(await request.get('/prompt_manager/gallery')).toBeOK();
|
||||
await gallery.click();
|
||||
await expect(page).toHaveURL(/\/prompt_manager\/gallery$/);
|
||||
await expect(page.locator('#workspace-main').getByRole('link', { name: 'Open Images', exact: true })).toBeVisible();
|
||||
await expect(page.locator('a[href*="/prompt_manager/admin"], a[href*="/prompt_manager/gallery"]')).toHaveCount(0);
|
||||
await expect(page.locator('#workspace-main').getByText('Your existing tools remain available')).toHaveCount(0);
|
||||
// The legacy pages themselves stay at their URLs.
|
||||
for (const legacy of ['/prompt_manager/admin', '/prompt_manager/gallery', '/prompt_manager/gallery.html']) {
|
||||
await expect(await request.get(legacy)).toBeOK();
|
||||
}
|
||||
});
|
||||
|
||||
test('/prompt_manager/web lands on the workspace Library and Back does not bounce through it (#525)', async ({ page }) => {
|
||||
await page.goto(`${workspace}#/overview`);
|
||||
await page.evaluate(() => { location.href = '/prompt_manager/web'; });
|
||||
await expect(page).toHaveURL(/\/prompt_manager\/workspace#\/library$/);
|
||||
const main = page.locator('#workspace-main');
|
||||
await expect(main.getByRole('heading', { level: 1, name: 'Prompt library', exact: true })).toBeVisible();
|
||||
await page.goBack();
|
||||
await expect(page).toHaveURL(/\/prompt_manager\/workspace#\/overview$/);
|
||||
await expect(main.getByRole('heading', { level: 1, name: 'Overview', exact: true })).toBeVisible();
|
||||
});
|
||||
|
||||
test('keyboard navigation, browser history and unknown routes remain usable', async ({ page }) => {
|
||||
|
||||
@@ -210,14 +210,14 @@ test(`${label} follows actual ComfyUI light and dark palettes`, async ({ page },
|
||||
}
|
||||
});
|
||||
|
||||
test(`${label} Web UI button opens actual standalone pages`, async ({ page }) => {
|
||||
test(`${label} Web UI button opens the workspace; legacy pages still load`, async ({ page }) => {
|
||||
await addEditor(page, type);
|
||||
const popupPromise = page.waitForEvent('popup');
|
||||
await page.locator('.pm-focused').getByRole('button', { name: 'Web UI ↗', exact: true }).click();
|
||||
const popup = await popupPromise;
|
||||
await popup.waitForLoadState('domcontentloaded');
|
||||
await expect(popup).toHaveURL(/\/prompt_manager\/(web|admin)/);
|
||||
await expect(popup.locator('body')).toContainText('Prompt');
|
||||
await expect(popup).toHaveURL(/\/prompt_manager\/workspace#\/library$/);
|
||||
await expect(popup.locator('#workspace-main h1')).toHaveText('Prompt library');
|
||||
await expect(page.locator('.pm-focused').getByRole('status').filter({ hasText: 'Web UI opened.' })).toHaveText('Web UI opened.');
|
||||
await popup.goto(new URL('/prompt_manager/gallery', page.url()).href);
|
||||
await expect(popup.locator('#addPromptBtn')).toBeVisible();
|
||||
|
||||
@@ -84,7 +84,7 @@ test('all node schemas and extension entrypoints load from the real host', async
|
||||
});
|
||||
|
||||
for (const type of ['PromptManager', 'PromptManagerText']) {
|
||||
test(`${type} Web UI button opens actual standalone pages`, async ({ page }) => {
|
||||
test(`${type} Web UI button opens the workspace; legacy pages still load`, async ({ page }) => {
|
||||
await page.evaluate(async type => {
|
||||
const { app } = await import('/scripts/app.js');
|
||||
const node = LiteGraph.createNode(type);
|
||||
@@ -95,14 +95,25 @@ for (const type of ['PromptManager', 'PromptManagerText']) {
|
||||
await page.getByRole('button', { name: '🌐 Web UI', exact: true }).click();
|
||||
const popup = await popupPromise;
|
||||
await popup.waitForLoadState('domcontentloaded');
|
||||
await expect(popup).toHaveURL(/\/prompt_manager\/(web|admin)/);
|
||||
await expect(popup.locator('body')).toContainText('Prompt');
|
||||
await expect(popup).toHaveURL(/\/prompt_manager\/workspace#\/library$/);
|
||||
await expect(popup.locator('#workspace-main h1')).toHaveText('Prompt library');
|
||||
await popup.goto(new URL('/prompt_manager/gallery', page.url()).href);
|
||||
await expect(popup.locator('#addPromptBtn')).toBeVisible();
|
||||
await popup.close();
|
||||
});
|
||||
}
|
||||
|
||||
test('top-bar PM button opens the workspace in either renderer', async ({ page }) => {
|
||||
const button = page.locator('.promptmanager-top-button-group button, #pm-toolbar-btn').first();
|
||||
await expect(button).toBeVisible();
|
||||
const popupPromise = page.waitForEvent('popup');
|
||||
await button.click();
|
||||
const popup = await popupPromise;
|
||||
await expect(popup).toHaveURL(/\/prompt_manager\/workspace#\/library$/);
|
||||
await expect(popup.locator('#workspace-main h1')).toHaveText('Prompt library');
|
||||
await popup.close();
|
||||
});
|
||||
|
||||
test('Classic controls follow actual ComfyUI light and dark palettes', async ({ page }, testInfo) => {
|
||||
await page.evaluate(async () => {
|
||||
const { app } = await import('/scripts/app.js');
|
||||
|
||||
@@ -37,11 +37,11 @@ test('workspace serves real library data beside either ComfyUI renderer', async
|
||||
await expect(workspace.getByText('No matching images · 0 images', { exact: true })).toBeVisible();
|
||||
await expect(workspace.locator('.gallery-body > .empty')).toHaveText('No matching images. Try another search or category.');
|
||||
}
|
||||
// D29: Images no longer links the legacy gallery; Overview's "Open existing image gallery" card does.
|
||||
// D29: Images no longer links the legacy gallery; #525: nor does Overview. The page stays at its URL.
|
||||
await expect(workspace.locator('#workspace-main a[href="/prompt_manager/gallery"]')).toHaveCount(0);
|
||||
await workspace.evaluate(() => { location.hash = '/overview'; });
|
||||
const gallery = workspace.locator('#workspace-main').getByRole('link', { name: 'Open existing image gallery', exact: true });
|
||||
await expect(gallery).toHaveAttribute('href', '/prompt_manager/gallery');
|
||||
const imagesCard = workspace.locator('#workspace-main').getByRole('link', { name: 'Open Images', exact: true });
|
||||
await expect(imagesCard).toHaveAttribute('href', '#/images');
|
||||
expect((await workspace.request.get('/prompt_manager/gallery')).ok()).toBe(true);
|
||||
await workspace.evaluate(() => { location.hash = '/images'; });
|
||||
await workspace.reload();
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// #525: where the canvas buttons open PromptManager (web/js/workspace-entry.js).
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const source = import('../../web/js/workspace-entry.js');
|
||||
const state = import('../../web/js/workspace/state.mjs');
|
||||
|
||||
test('the canvas entry route is the workspace Library, a native route', async () => {
|
||||
const { WORKSPACE_ENTRY_ROUTE } = await source;
|
||||
const { resolveRoute } = await state;
|
||||
assert.equal(WORKSPACE_ENTRY_ROUTE, '/prompt_manager/workspace#/library');
|
||||
const [pathname, hash] = WORKSPACE_ENTRY_ROUTE.split('#');
|
||||
assert.equal(pathname, '/prompt_manager/workspace');
|
||||
assert.equal(resolveRoute(`#${hash}`).id, 'library');
|
||||
});
|
||||
|
||||
test('/prompt_manager/web redirects to the same route, under any mount prefix', async () => {
|
||||
const { WORKSPACE_ENTRY_ROUTE } = await source;
|
||||
const html = fs.readFileSync(path.join(__dirname, '../../web/index.html'), 'utf8');
|
||||
const [, pattern, flags, replacement] = html.match(/location\.replace\(\s*window\.location\.pathname\.replace\(\/(.+)\/([a-z]*), '([^']+)'\)/);
|
||||
const redirect = pathname => pathname.replace(new RegExp(pattern, flags), replacement);
|
||||
assert.equal(redirect('/prompt_manager/web'), WORKSPACE_ENTRY_ROUTE);
|
||||
assert.equal(redirect('/prompt_manager/web/'), WORKSPACE_ENTRY_ROUTE);
|
||||
assert.equal(redirect('/prompt_manager/index.html'), WORKSPACE_ENTRY_ROUTE);
|
||||
assert.equal(redirect('/sub/prompt_manager/web'), `/sub${WORKSPACE_ENTRY_ROUTE}`);
|
||||
});
|
||||
|
||||
test('no canvas entry point still names a legacy page', () => {
|
||||
for (const file of ['web/comfy/prompt_manager.js', 'web/js/focused-node.js', 'web/js/classic-node.js']) {
|
||||
const code = fs.readFileSync(path.join(__dirname, '../..', file), 'utf8');
|
||||
assert.doesNotMatch(code, /\/prompt_manager\/(?:admin|gallery|web)\b/, file);
|
||||
}
|
||||
});
|
||||
@@ -17,7 +17,7 @@ test('tags is a native route titled Tags; only unbuilt routes hand off to legacy
|
||||
const tags = resolveRoute('#/tags');
|
||||
assert.equal(tags.label, 'Tags & categories');
|
||||
assert.equal(routeTitle(tags), 'Tags');
|
||||
assert.equal(tags.href, '/prompt_manager/admin#/tags', 'Overview keeps the existing tag manager card');
|
||||
assert.equal(tags.href, '#/tags', 'Overview opens the native page (#525)');
|
||||
for (const id of ['images', 'library', 'inspect', 'overview', 'system']) {
|
||||
const route = resolveRoute(`#/${id}`);
|
||||
assert.equal(routeTitle(route), route.label);
|
||||
@@ -48,7 +48,6 @@ test('system resolves its sections; #/overview stays the production hub', async
|
||||
// Q12: Overview's System card opens the native System Overview, with no legacy href or action.
|
||||
const card = routes.find(route => route.id === 'system');
|
||||
assert.equal(card.href, '#/system/overview');
|
||||
assert.equal(card.native, true);
|
||||
assert.equal(card.action, 'Open System');
|
||||
});
|
||||
|
||||
@@ -77,9 +76,21 @@ test('inspect is the native Inspect a file page with the concept description', a
|
||||
// The concept's heading description (app.js renderMetadata 743).
|
||||
assert.equal(inspect.description, 'A standalone file uses the same image viewer and prompt actions.');
|
||||
assert.equal(isHandoff(inspect), false);
|
||||
// Overview keeps its card for the legacy metadata page, which stays working.
|
||||
assert.equal(inspect.href, '/prompt_manager/gallery.html');
|
||||
assert.equal(inspect.action, 'Open existing metadata inspector');
|
||||
// Overview's card opens the native page (#525); the legacy metadata page stays at its URL.
|
||||
assert.equal(inspect.href, '#/inspect');
|
||||
assert.equal(inspect.action, 'Open Inspect a file');
|
||||
});
|
||||
|
||||
test('every Overview card opens its own native page, never a legacy one (#525)', async () => {
|
||||
const { resolveRoute, routes } = await source;
|
||||
const cards = routes.filter(route => route.href);
|
||||
assert.deepEqual(cards.map(card => card.id), ['images', 'library', 'tags', 'inspect', 'system']);
|
||||
for (const card of cards) {
|
||||
assert.match(card.href, /^#\//, card.id);
|
||||
assert.equal(resolveRoute(card.href).id, card.id);
|
||||
assert.match(card.action, /^Open /, card.id);
|
||||
assert.doesNotMatch(card.action, /existing/, card.id);
|
||||
}
|
||||
});
|
||||
|
||||
test('a job of the Activity page is its own deep link', async () => {
|
||||
|
||||
@@ -107,6 +107,12 @@
|
||||
"functions": 98,
|
||||
"statements": 98
|
||||
},
|
||||
"workspace-entry.js": {
|
||||
"lines": 80,
|
||||
"branches": 80,
|
||||
"functions": 80,
|
||||
"statements": 80
|
||||
},
|
||||
"save-to-library.js": {
|
||||
"lines": 98,
|
||||
"branches": 98,
|
||||
|
||||
@@ -7,6 +7,7 @@ const { installFocusedNode, installSplitNode, installPairNode } = await import(a
|
||||
const { installPromptBridge } = await import(api.fileURL('/prompt_manager/js/prompt-bridge-host.js'));
|
||||
const { collapseLinkedWidget, isLinked, linkNoteText, primitiveTextValue, storedWidgetValue } = await import(api.fileURL('/prompt_manager/js/text-links.js'));
|
||||
const { normalizeSaveWidget } = await import(api.fileURL('/prompt_manager/js/save-to-library.js'));
|
||||
const { WORKSPACE_ENTRY_ROUTE } = await import(api.fileURL('/prompt_manager/js/workspace-entry.js'));
|
||||
const promptManagerTypes = new Set(['PromptManager', 'PromptManagerText', 'PromptManagerFocused', 'PromptManagerSplit', 'PromptManagerPair']);
|
||||
|
||||
// ============================================================================
|
||||
@@ -109,7 +110,7 @@ function addPMTopBarButton() {
|
||||
// Create PM button
|
||||
const pmButton = $el("button", {
|
||||
classList: "comfyui-button comfyui-menu-mobile-collapse primary",
|
||||
title: "PromptManager - Open Admin Dashboard",
|
||||
title: "PromptManager - Open workspace",
|
||||
style: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
@@ -125,7 +126,7 @@ function addPMTopBarButton() {
|
||||
transition: "background 0.2s",
|
||||
},
|
||||
onclick: () => {
|
||||
window.open(api.fileURL("/prompt_manager/admin"), "_blank");
|
||||
window.open(api.fileURL(WORKSPACE_ENTRY_ROUTE), "_blank");
|
||||
},
|
||||
onmouseenter: (e) => { e.target.style.background = "#0d9488"; },
|
||||
onmouseleave: (e) => { e.target.style.background = "#14b8a6"; },
|
||||
@@ -161,7 +162,7 @@ function addPMTopBarButtonLegacy() {
|
||||
const btn = document.createElement("button");
|
||||
btn.id = "pm-toolbar-btn";
|
||||
btn.innerText = "PM";
|
||||
btn.title = "Open PromptManager Admin Dashboard";
|
||||
btn.title = "Open PromptManager workspace";
|
||||
|
||||
// Teal button styling
|
||||
btn.style.cssText = `
|
||||
@@ -181,7 +182,7 @@ function addPMTopBarButtonLegacy() {
|
||||
btn.onmouseleave = () => btn.style.background = "#14b8a6";
|
||||
|
||||
btn.onclick = () => {
|
||||
window.open(api.fileURL("/prompt_manager/admin"), "_blank");
|
||||
window.open(api.fileURL(WORKSPACE_ENTRY_ROUTE), "_blank");
|
||||
};
|
||||
|
||||
topBar.appendChild(btn);
|
||||
@@ -1184,9 +1185,7 @@ app.registerExtension({
|
||||
// Method to open web interface
|
||||
nodeType.prototype.openWebInterface = function () {
|
||||
try {
|
||||
// Open the web interface served via our API endpoint
|
||||
const currentOrigin = window.location.origin;
|
||||
const webUrl = `${currentOrigin}/prompt_manager/web`;
|
||||
const webUrl = api.fileURL(WORKSPACE_ENTRY_ROUTE);
|
||||
|
||||
if (this.properties.webuiDisplayMode === "newtab") {
|
||||
// Open in new tab without window options
|
||||
@@ -1198,7 +1197,8 @@ app.registerExtension({
|
||||
"success",
|
||||
);
|
||||
} else {
|
||||
window.location.href = webUrl;
|
||||
// Never navigate the canvas away: it may hold unsaved work.
|
||||
this.showNotification("Allow popups to open Web UI, then try again.", "error");
|
||||
}
|
||||
} else {
|
||||
// Default popup mode with specific dimensions
|
||||
@@ -1211,7 +1211,8 @@ app.registerExtension({
|
||||
if (newWindow) {
|
||||
this.showNotification("Web interface opened in popup", "success");
|
||||
} else {
|
||||
window.location.href = webUrl;
|
||||
// Never navigate the canvas away: it may hold unsaved work.
|
||||
this.showNotification("Allow popups to open Web UI, then try again.", "error");
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
+5
-5
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PromptManager - Redirecting to Admin</title>
|
||||
<title>PromptManager - Opening the workspace</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
@@ -49,13 +49,13 @@
|
||||
<div class="container">
|
||||
<div class="spinner"></div>
|
||||
<h1>🔍 PromptManager</h1>
|
||||
<p>Redirecting to Admin Dashboard...</p>
|
||||
<p><small>Loading the full-featured prompt management interface</small></p>
|
||||
<p>Opening the workspace...</p>
|
||||
<noscript><p><a href="workspace#/library">Open the workspace</a></p></noscript>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Redirect to admin interface immediately
|
||||
window.location.href = window.location.pathname.replace(/\/(?:web|index\.html)\/?$/, '/admin');
|
||||
// Replace, so Back doesn't return here and bounce. Same route as web/js/workspace-entry.js (#525).
|
||||
window.location.replace(window.location.pathname.replace(/\/(?:web|index\.html)\/?$/, '/workspace#/library'));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {createScope, createButton, createField, mountTheme} from './shared/toolkit/core.js';
|
||||
import {createPromptLibrary} from './prompt-library.js';
|
||||
import {WORKSPACE_ENTRY_ROUTE} from './workspace-entry.js';
|
||||
import {attachMetadataAutocomplete} from './metadata-autocomplete.js';
|
||||
import {exposeTextSockets, linkNoteText, preferTextInputs, textInputAtPoint} from './text-links.js';
|
||||
import {SAVE_WIDGET, normalizeSaveWidget, placeSaveWidgetLast, saveWidget, savesToLibrary, setSaveToLibrary} from './save-to-library.js';
|
||||
@@ -253,7 +254,7 @@ export function installFocusedNode(nodeType, app, api, parseQuery, {layout = 'fo
|
||||
}
|
||||
});
|
||||
scope.listen(web,'click',() => {
|
||||
const url = api.fileURL('/prompt_manager/web');
|
||||
const url = api.fileURL(WORKSPACE_ENTRY_ROUTE);
|
||||
const opened = webDisplayMode === 'newtab' ? window.open(url,'_blank') : window.open(url,'_blank','width=1200,height=800,scrollbars=yes,resizable=yes');
|
||||
footer.setAttribute('role',opened ? 'status' : 'alert');
|
||||
footer.textContent = opened ? 'Web UI opened.' : 'Allow popups to open Web UI, then try again.';
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
// Where the canvas's PM and Web UI buttons open PromptManager (#525). web/index.html redirects to the same route.
|
||||
export const WORKSPACE_ENTRY_ROUTE = '/prompt_manager/workspace#/library';
|
||||
@@ -34,10 +34,9 @@ function element(tag, text, className) {
|
||||
return node;
|
||||
}
|
||||
|
||||
function legacyLink(route) {
|
||||
function cardLink(route) {
|
||||
const link = element('a', route.action, 'workspace-link');
|
||||
// A native card (System, Q12) links its workspace hash; a legacy tool's page goes through the mount prefix.
|
||||
link.href = route.native ? route.href : resolveUrl(route.href);
|
||||
link.href = route.href;
|
||||
return link;
|
||||
}
|
||||
|
||||
@@ -485,11 +484,11 @@ function mountWorkspace() {
|
||||
stats.setAttribute('aria-label', 'Library statistics');
|
||||
main.append(stats);
|
||||
const tools = element('section', undefined, 'surface');
|
||||
tools.append(element('h2', 'Continue creating'), element('p', 'Your existing tools remain available while the new workspaces take shape.'));
|
||||
tools.append(element('h2', 'Continue creating'), element('p', 'Pick up where you left off in your images, prompts and tools.'));
|
||||
const cards = element('div', undefined, 'tool-grid');
|
||||
for (const target of routes.filter(item => item.href)) {
|
||||
const card = element('article', undefined, 'tool-card');
|
||||
card.append(element('h3', target.label), element('p', target.description, 'small'), legacyLink(target));
|
||||
card.append(element('h3', target.label), element('p', target.description, 'small'), cardLink(target));
|
||||
cards.append(card);
|
||||
}
|
||||
tools.append(cards);
|
||||
|
||||
+14
-14
@@ -1,14 +1,15 @@
|
||||
export const routes = [
|
||||
{ id: 'images', label: 'Images', href: '/prompt_manager/gallery',
|
||||
description: 'Browse your images, inspect their metadata and review generated tags.', action: 'Open existing image gallery' },
|
||||
{ id: 'library', label: 'Prompt library', href: '/prompt_manager/admin',
|
||||
description: 'Find, edit and organize the prompts already in your library.', action: 'Open existing prompt library' },
|
||||
// Native screen; `title` is the concept's breadcrumb/title, the href stays for Overview's card.
|
||||
{ id: 'tags', label: 'Tags & categories', title: 'Tags', href: '/prompt_manager/admin#/tags',
|
||||
description: 'Explore your vocabulary and find the prompts associated with each tag.', action: 'Open existing tag manager' },
|
||||
// Native screen (#249 local Inspect); the href stays for Overview's card to the legacy metadata page.
|
||||
{ id: 'inspect', label: 'Inspect a file', href: '/prompt_manager/gallery.html',
|
||||
description: 'A standalone file uses the same image viewer and prompt actions.', action: 'Open existing metadata inspector' },
|
||||
// `href` and `action` are Overview's card (#525): every card opens its native page, never a legacy one.
|
||||
{ id: 'images', label: 'Images', href: '#/images',
|
||||
description: 'Browse your images, inspect their metadata and review generated tags.', action: 'Open Images' },
|
||||
{ id: 'library', label: 'Prompt library', href: '#/library',
|
||||
description: 'Find, edit and organize the prompts already in your library.', action: 'Open Prompt library' },
|
||||
// `title` is the concept's breadcrumb/title.
|
||||
{ id: 'tags', label: 'Tags & categories', title: 'Tags', href: '#/tags',
|
||||
description: 'Explore your vocabulary and find the prompts associated with each tag.', action: 'Open Tags & categories' },
|
||||
// #249 local Inspect.
|
||||
{ id: 'inspect', label: 'Inspect a file', href: '#/inspect',
|
||||
description: 'A standalone file uses the same image viewer and prompt actions.', action: 'Open Inspect a file' },
|
||||
{ id: 'overview', label: 'Overview' },
|
||||
// SAVED VIEWS' first row (#249 D51): Images filtered to favorites. No href, so Overview lists no card for it.
|
||||
{ id: 'favorites', label: 'Favorites' },
|
||||
@@ -18,10 +19,9 @@ export const routes = [
|
||||
{ id: 'review', label: 'Needs review', title: 'Review' },
|
||||
// The concept's Activity (app.js renderActivity 732–738): this server session's jobs. No href: no Overview card.
|
||||
{ id: 'activity', label: 'Activity' },
|
||||
// Native System (#249 System S2): `#/system/<section>`. Q12: Overview's card opens its Overview section (`native`:
|
||||
// a workspace hash, not a legacy page); every section is native (S12 retired D94's hand-off). The description is
|
||||
// the concept's System heading (app.js renderSystem 700).
|
||||
{ id: 'system', label: 'System', href: '#/system/overview', native: true,
|
||||
// Native System (#249 System S2): `#/system/<section>`. Q12: Overview's card opens its Overview section; every
|
||||
// section is native (S12 retired D94's hand-off). The description is the concept's System heading (app.js renderSystem 700).
|
||||
{ id: 'system', label: 'System', href: '#/system/overview',
|
||||
description: 'Configuration, maintenance and recovery—with browsing kept in the workspace.', action: 'Open System' },
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user