Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ version check rather than run against what it no longer has.

### Added

- **What a double-click on a node opens is a contribution point:
`NodeOpener`** (exported). An opener answers before the platform's own
definition navigation, or leaves the node to it; one per `id`, the last
binding winning. The `@viewer` half of the double-click listener (open,
diff, open with an editor or run a command on a task's last tokens, or a
path it declares) is the `viewer` opener (`viewer-node-opener.ts`), which
the stock module binds until wfpy-ide binds its own.

- **What a product's webview code needs, from the package.**
`@dialogram/diagram-client` exports `VscodeUi` (the host's quick picks,
input boxes and commands), `clientBehavior`, `commandId`, `offersRun`,
Expand Down
9 changes: 9 additions & 0 deletions packages/diagram-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,15 @@ bundle is an example of all three: its `src/webview/agent-ui/` holds the
`@tool` and `@agent` editors, the agent sections (Basic Info's Skill row,
Agent Tools, the live Agent Context) and the `@tool` and `@viewer` templates.

**Node openers.** A double-click on a node opens its definition, unless the
network navigation opens something for it. To open something else for some
of your nodes, bind a `NodeOpener` with an `id`: its `open(node, { sourceUri })`
returns the actions to run, or `undefined` to leave the node to the platform.
Openers are asked in the order their ids were first bound, and the first
that answers wins; a later binding for an id replaces the earlier one. The
stock bundle binds wfpy's `@viewer` opener (id `viewer`) until wfpy-ide binds
its own.

**A diagram of your own.** When you compose the container yourself:

```ts
Expand Down
1 change: 1 addition & 0 deletions packages/diagram-client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export {
type PanelModelRoot
} from './node-section';
export { createPropertySection, escapeHtml, shortenPath } from './property-section';
export { NodeOpener, type NodeOpenerContext } from './node-opener';
export {
AnnotationTemplate,
CUSTOM_ANNOTATION_ENTRY,
Expand Down
37 changes: 37 additions & 0 deletions packages/diagram-client/src/node-opener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Node openers: the contribution point for what a double-click on a node
* opens.
*
* The platform opens a node's definition (preview), unless the network
* navigation listener already opens something for it. An opener a product
* binds is asked first: it answers with the actions that open what the node
* stands for in that product, or `undefined` to leave the node to the
* platform. A product binds its openers in a feature module:
*
* bind(NodeOpener).toConstantValue(myOpener);
*
* There is one opener per `id`: a later binding for an id replaces the
* earlier one, and an opener without an id is dropped. Openers are asked in
* the order their ids were first bound; the first that answers with actions
* wins (an empty list is no answer), and one that throws is skipped.
*
* The network navigation listener may open something for the same
* double-click (a node's first view); the platform's definition stands back
* for it, but an opener's answer does not. An opener should leave such nodes
* unless it means to open its own thing beside the view.
*/
import type { Action, GModelElement } from '@eclipse-glsp/sprotty';

export const NodeOpener = Symbol.for('dialogram.NodeOpener');

export interface NodeOpenerContext {
/** The file the diagram was opened from, if any. */
readonly sourceUri: string | undefined;
}

export interface NodeOpener {
/** What the opener is, for a later binding to replace it. */
readonly id: string;
/** The actions a double-click on `node` runs, or `undefined` (or none) to leave it to the platform. */
open(node: GModelElement, context: NodeOpenerContext): Array<Action | Promise<Action>> | undefined;
}
8 changes: 7 additions & 1 deletion packages/diagram-client/src/stock-features.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { clientBehavior } from './profile';
import { WorkflowNavigationUi } from './navigation-ui';
import { WorkflowNetworkNavigationMouseListener } from './network-navigation-mouse-listener';
import { ViewerMouseListener } from './viewer-mouse-listener';
import { NodeOpener } from './node-opener';
import { ViewerNodeOpener } from './viewer-node-opener';
import { EdgeOpenMouseListener } from './edge-open-mouse-listener';
import { WhitespaceRootPropertiesMouseListener } from './whitespace-root-properties-mouse-listener';
import { WorkflowElkLiveDragRouter } from './elk-live-drag-router';
Expand Down Expand Up @@ -111,8 +113,12 @@ export const workflowFeaturesModule = new ContainerModule((bind, unbind, isBound
// Double-click navigation into nested networks.
bindAsService(context, TYPES.MouseListener, WorkflowNetworkNavigationMouseListener);

// Double-click on @viewer external tasks to open/diff the last token files.
// Double-click on a node: what a product's openers open for it, else its
// definition (every product relies on the latter).
bindAsService(context, TYPES.MouseListener, ViewerMouseListener);
// What a double-click on a `@viewer` task opens (its last tokens), until
// the product that has `@viewer` binds it in its own bundle.
bind(NodeOpener).toConstantValue(new ViewerNodeOpener());

// Double-click on diagram whitespace to show root/workflow properties.
bindAsService(context, TYPES.MouseListener, WhitespaceRootPropertiesMouseListener);
Expand Down
Loading
Loading