Skip to content
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

Add details panel for graph selection info #22

Merged
merged 9 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
44 changes: 44 additions & 0 deletions vscode/images/Auxon_symbol.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions vscode/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@
"title": "Auxon Deviant",
"icon": "images/Deviant_symbol.svg"
}
],
"panel": [
{
"id": "auxon-details-panel",
"title": "Auxon",
"icon": "images/Auxon_symbol.svg"
}
]
},
"views": {
Expand Down Expand Up @@ -135,6 +142,13 @@
"id": "auxon.experiments",
"name": "Experiments"
}
],
"auxon-details-panel": [
{
"type": "webview",
"id": "auxon.details.panelView",
"name": "Auxon"
}
]
},
"menus": {
Expand Down Expand Up @@ -863,6 +877,10 @@
{
"command": "auxon.experiments.hideResults",
"title": "✓ Show Results"
},
{
"command": "auxon.details.show",
"title": "Show details in the details panel"
}
],
"configuration": {
Expand Down
131 changes: 131 additions & 0 deletions vscode/resources/detailsPanel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
(function () {
const vscode = acquireVsCodeApi();

const eventsHeader = document.getElementById("eventsHeader");
const eventDetails = document.getElementById("eventDetails");
const timelinesHeader = document.getElementById("timelinesHeader");
const timelineDetails = document.getElementById("timelineDetails");
const interactionsHeader = document.getElementById("interactionsHeader");
const interactionDetails = document.getElementById("interactionDetails");

window.addEventListener("message", (event) => {
const message = event.data;
clearAll();
if (message.events !== undefined && message.events.length > 0) {
constructEvents(message.events);
}
if (message.timelines !== undefined && message.timelines.length > 0) {
constructTimelines(message.timelines);
}
if (message.interactions !== undefined && message.interactions.length > 0) {
constructInteractions(message.interactions);
}
});

clearAll();

function clearAll() {
clearEvents();
clearTimelines();
clearInteractions();
}

function clearEvents() {
eventsHeader.hidden = true;
eventsHeader.innerHTML = "";
eventDetails.hidden = true;
eventDetails.rowsData = [];
}

function constructEvents(events) {
eventsHeader.innerHTML = "Events";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the whole element is being hidden, we can probably put the content for these headers in the template itself, and not worry about having to set innerHTML

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this, but they still show up on initialization when the panel loads. Not sure why, but I assume it's something to do with the flex box display/styling.

eventsHeader.hidden = false;

eventDetails.columnDefinitions = [
{ columnDataKey: "Col0", title: "Event Name" },
{ columnDataKey: "Col1", title: "Timeline Name" },
{ columnDataKey: "Col2", title: "Timeline ID" },
{ columnDataKey: "Col3", title: "Count" },
];
eventDetails.rowsData = events.map((ev) => {
return { Col0: ev.name, Col1: ev.timeline.name, Col2: ev.timeline.id, Col3: ev.count };
});
eventDetails.hidden = false;
}

function clearTimelines() {
timelinesHeader.hidden = true;
timelinesHeader.innerHTML = "";
timelineDetails.hidden = true;
timelineDetails.rowsData = [];
}

function constructTimelines(timelines) {
timelinesHeader.innerHTML = "Timelines";
timelinesHeader.hidden = false;

timelineDetails.columnDefinitions = [
{ columnDataKey: "Col0", title: "Timeline Name" },
{ columnDataKey: "Col1", title: "Timeline ID" },
];
timelineDetails.rowsData = timelines.map((tl) => {
return { Col0: tl.name, Col1: tl.id };
});
timelineDetails.hidden = false;
}

function clearInteractions() {
interactionsHeader.hidden = true;
interactionsHeader.innerHTML = "";
interactionDetails.hidden = true;
interactionDetails.rowsData = [];
}

function constructInteractions(interactions) {
interactionsHeader.innerHTML = "Interactions";
interactionsHeader.hidden = false;

const containsEvents = interactions.some(
(it) => it.sourceEvent !== undefined || it.destinationEvent !== undefined
);

interactionDetails.columnDefinitions = [];
if (containsEvents) {
interactionDetails.columnDefinitions.push({ columnDataKey: "Col0", title: "Source Event Name" });
}
interactionDetails.columnDefinitions.push(
{ columnDataKey: "Col1", title: "Source Timeline Name" },
{ columnDataKey: "Col2", title: "Source Timeline ID" }
);
if (containsEvents) {
interactionDetails.columnDefinitions.push({ columnDataKey: "Col3", title: "Destination Event Name" });
}
interactionDetails.columnDefinitions.push(
{ columnDataKey: "Col4", title: "Destination Timeline Name" },
{ columnDataKey: "Col5", title: "Destination Timeline ID" },
{ columnDataKey: "Col6", title: "Count" }
);
interactionDetails.rowsData = interactions.map((it) => {
if (containsEvents) {
return {
Col0: it.sourceEvent,
Col1: it.sourceTimeline.name,
Col2: it.sourceTimeline.id,
Col3: it.destinationEvent,
Col4: it.destinationTimeline.name,
Col5: it.destinationTimeline.id,
Col6: it.count,
};
} else {
return {
Col1: it.sourceTimeline.name,
Col2: it.sourceTimeline.id,
Col4: it.destinationTimeline.name,
Col5: it.destinationTimeline.id,
Col6: it.count,
};
}
});
interactionDetails.hidden = false;
}
})();
17 changes: 17 additions & 0 deletions vscode/resources/transitionGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,21 @@
}
}

function postShowSelectionDetails() {
const selectedNodes = cy
.nodes()
.filter((n) => n.hasClass("selected"))
.map((n) => n.id());
const selectedEdges = cy
.edges()
.filter((e) => e.hasClass("selected"))
.map((e) => e.data("idx"));
vscode.postMessage({
command: "showDetailsForSelection",
data: { nodes: selectedNodes, edges: selectedEdges },
});
}

function constructGraph() {
if (nodeElements.length == 0 && edgeElements.length == 0) {
// Do nothing until we've gotten data from the vscode
Expand Down Expand Up @@ -306,6 +321,7 @@
item.addClass("selected").predecessors().addClass("selected");
break;
}
postShowSelectionDetails();
});
cy.on("unselect", function (evt) {
let item = evt.target;
Expand All @@ -319,6 +335,7 @@
cy.elements().removeClass("selected");
break;
}
postShowSelectionDetails();
});
cy.on("cxttap", function (evt) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cxttap? 🙄

const numSelectedNodes = cy
Expand Down
85 changes: 85 additions & 0 deletions vscode/src/detailsPanel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import * as vscode from "vscode";
import * as fs from "fs";
import * as handlebars from "handlebars";
import * as api from "./modalityApi";
import { getNonce } from "./webviewUtil";

// N.B. to clear the details panel, provide an empty object or empty arrays
export interface ShowDetailsParams {
events?: EventDetails[];
timelines?: TimelineDetails[];
interactions?: InteractionDetails[];
}

export interface InteractionDetails {
sourceEvent?: string;
sourceTimeline: TimelineDetails;
destinationEvent?: string;
destinationTimeline: TimelineDetails;
count?: number;
}

export interface TimelineDetails {
id: string;
name?: string;
}

export interface EventDetails {
name: string;
timeline: TimelineDetails;
count?: number;
}

export class DetailsPanelProvider implements vscode.WebviewViewProvider {
public static readonly viewType = "auxon.details.panelView";
private view?: vscode.WebviewView = undefined;

constructor(private readonly apiClient: api.Client, private readonly extensionUri: vscode.Uri) {}

public register(context: vscode.ExtensionContext) {
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(DetailsPanelProvider.viewType, this),
vscode.commands.registerCommand("auxon.details.show", (params) => this.showDetails(params))
);
}

public resolveWebviewView(
webviewView: vscode.WebviewView,
_context: vscode.WebviewViewResolveContext,
_token: vscode.CancellationToken
) {
this.view = webviewView;

webviewView.webview.options = {
enableScripts: true,
};

webviewView.webview.html = this.generateHtmlContent(webviewView.webview);
}

private generateHtmlContent(webview: vscode.Webview): string {
const webviewUiToolkitJsUri = webview.asWebviewUri(
vscode.Uri.joinPath(this.extensionUri, "resources", "dist", "webviewuitoolkit.min.js")
);
const detailsPanelJsUri = webview.asWebviewUri(
vscode.Uri.joinPath(this.extensionUri, "resources", "detailsPanel.js")
);

const templateUri = vscode.Uri.joinPath(this.extensionUri, "templates", "detailsPanel.html");
const templateText = fs.readFileSync(templateUri.fsPath, "utf8");
const template = handlebars.compile(templateText);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably load/compile the template in a less granular scope, like maybe the panel constructor.


const html = template({
cspSource: webview.cspSource,
nonce: getNonce(),
webviewUiToolkitJsUri,
detailsPanelJsUri,
});

return html;
}

private showDetails(params: ShowDetailsParams) {
this.view.webview.postMessage(params);
}
}
3 changes: 3 additions & 0 deletions vscode/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import * as mutators from "./mutators";
import * as mutations from "./mutations";
import * as deviantCommands from "./deviantCommands";
import * as experiments from "./experiments";
import * as detailsPanel from "./detailsPanel";

export let log: vscode.OutputChannel;
let lspClient: LanguageClient;
Expand Down Expand Up @@ -69,6 +70,7 @@ export async function activate(context: vscode.ExtensionContext) {
const mutatorsTreeDataProvider = new mutators.MutatorsTreeDataProvider(apiClient);
const mutationsTreeDataProvider = new mutations.MutationsTreeDataProvider(apiClient);
const experimentsTreeDataProvider = new experiments.ExperimentsTreeDataProvider(apiClient);
const detailsPanelProvider = new detailsPanel.DetailsPanelProvider(apiClient, context.extensionUri);

workspacesTreeDataProvider.onDidChangeActiveWorkspace((ws_ver) => {
log.appendLine(`Active workspace change! ${ws_ver}`);
Expand Down Expand Up @@ -109,6 +111,7 @@ export async function activate(context: vscode.ExtensionContext) {
mutatorsTreeDataProvider.register(context);
mutationsTreeDataProvider.register(context);
experimentsTreeDataProvider.register(context);
detailsPanelProvider.register(context);
}

export function deactivate(): Thenable<void> | undefined {
Expand Down
Loading
Loading