mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 14:23:28 +07:00
plugin graph viewer - tooltips
GitOrigin-RevId: 02f26829ad706f1418a1463afac1866e062b9fe1
This commit is contained in:
committed by
intellij-monorepo-bot
parent
f99d081f68
commit
5abfa8be7e
3
docs/.gitignore
vendored
Normal file
3
docs/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
out/plugin-graph
|
||||
plugin-graph/node_modules
|
||||
.DS_Store
|
||||
@@ -1,68 +0,0 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Plugin Graph</title>
|
||||
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
|
||||
|
||||
<link rel="preconnect" href="https://cdn.jsdelivr.net">
|
||||
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
|
||||
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/cytoscape@3.19.0/dist/cytoscape.min.js"
|
||||
integrity="sha256-Q5aff3EY3fW5pgDK43AnI19LbbryZdLXCmsipdPDwWM=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/flexsearch@0.6.32/dist/flexsearch.min.js"
|
||||
integrity="sha256-RbOWIPK8D/wzx66aEmf1i32S511+EQGFKxtXoepYKhA=" crossorigin="anonymous"></script>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/layout-base@2.0.0/layout-base.js"
|
||||
integrity="sha256-z6wEGjx+aPziAQlCx29FoQUoSy+k/VCVUzNSZJAncP8=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/cose-base@2.0.0/cose-base.js" integrity="sha256-l5cuNpyJp9j3JcIsngoR2+prUPZSg8FbXihfXCrwSDc="
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/cytoscape-fcose@2.0.0/cytoscape-fcose.js"
|
||||
integrity="sha256-J/3ZxWPNEF36ZZz7xnSZCC/QkH8VfgjQBNtktLRXjGU=" crossorigin="anonymous"></script>
|
||||
|
||||
<script src="plugin-graph.js" type="text/javascript"></script>
|
||||
<!--suppress CssUnusedSymbol -->
|
||||
<style>
|
||||
.tooltipMainValue {
|
||||
float: right;
|
||||
margin-left: 20px;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.tooltipValue {
|
||||
float: right;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.tooltipSelectableValue {
|
||||
float: right;
|
||||
margin-left: 20px;
|
||||
user-select: text
|
||||
}
|
||||
|
||||
.abcC {
|
||||
position: absolute;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
z-index: 100;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="abcC">
|
||||
<input id="searchField" type="search" placeholder="filter"
|
||||
spellcheck="false"
|
||||
autofocus/>
|
||||
</div>
|
||||
<div id="cy" style="width:100%; height:100%;">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,289 +0,0 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
fetch("plugin-graph.json")
|
||||
.then(it => it.json())
|
||||
.then(graph => {
|
||||
const listener = () => initGraph(graph)
|
||||
if (document.readyState === "loading") {
|
||||
window.addEventListener("DOMContentLoaded", listener)
|
||||
}
|
||||
else {
|
||||
listener()
|
||||
}
|
||||
})
|
||||
|
||||
function getItemSizeStyle(factor) {
|
||||
const baseNodeDiameter = 10
|
||||
const size = `${Math.ceil(baseNodeDiameter * factor)}px`
|
||||
return {
|
||||
"width": size,
|
||||
"height": size,
|
||||
}
|
||||
}
|
||||
|
||||
function initGraph(graph) {
|
||||
// noinspection SpellCheckingInspection
|
||||
const layoutOptions = {
|
||||
name: "fcose",
|
||||
quality: "proof",
|
||||
randomize: false,
|
||||
nodeDimensionsIncludeLabels: true,
|
||||
}
|
||||
|
||||
// noinspection SpellCheckingInspection
|
||||
const cy = cytoscape({
|
||||
container: document.getElementById("cy"),
|
||||
elements: graph,
|
||||
layout: layoutOptions,
|
||||
minZoom: 0.4,
|
||||
maxZoom: 3,
|
||||
autoungrabify: true,
|
||||
|
||||
style: [
|
||||
{
|
||||
selector: "node",
|
||||
style: {
|
||||
"label": (e) => {
|
||||
const name = e.data("name")
|
||||
if (name.startsWith("intellij.")) {
|
||||
return `i.${name.substring("intellij.".length)}`
|
||||
}
|
||||
else if (name.startsWith("com.intellij.modules.")) {
|
||||
return `c.i.m.${name.substring("com.intellij.modules.".length)}`
|
||||
}
|
||||
else {
|
||||
return name
|
||||
}
|
||||
},
|
||||
"font-family": "JetBrains Mono",
|
||||
"font-size": 13,
|
||||
"color": "#515151",
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: "node[type=0]",
|
||||
style: getItemSizeStyle(1),
|
||||
},
|
||||
{
|
||||
selector: "node[type=1]",
|
||||
style: getItemSizeStyle(1.2),
|
||||
},
|
||||
{
|
||||
selector: "node[type=2]",
|
||||
style: getItemSizeStyle(1.4),
|
||||
},
|
||||
{
|
||||
selector: "edge",
|
||||
style: {
|
||||
"curve-style": "straight",
|
||||
"width": 1,
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: "edge[type=0]",
|
||||
style: {
|
||||
"target-arrow-shape": "triangle-backcurve",
|
||||
"arrow-scale": 0.8,
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: "edge[type=1]",
|
||||
style: {
|
||||
"target-arrow-shape": "square",
|
||||
// square is too big
|
||||
"arrow-scale": 0.5,
|
||||
},
|
||||
},
|
||||
|
||||
// highlighting (https://stackoverflow.com/a/38468892)
|
||||
{
|
||||
selector: "node.semiTransparent",
|
||||
style: {"opacity": "0.5"}
|
||||
},
|
||||
{
|
||||
selector: "edge.highlight",
|
||||
style: {"mid-target-arrow-color": "#FFF"}
|
||||
},
|
||||
{
|
||||
selector: "edge.semiTransparent",
|
||||
style: {"opacity": "0.2"}
|
||||
},
|
||||
|
||||
{
|
||||
selector: "node.found",
|
||||
style: {
|
||||
"font-weight": "600",
|
||||
},
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
// ensure that dragging of element causes panning and not selecting
|
||||
// https://github.com/cytoscape/cytoscape.js/issues/1905
|
||||
cy.elements().panify()
|
||||
|
||||
function debounce(func) {
|
||||
let timeout
|
||||
return function (...args) {
|
||||
clearTimeout(timeout)
|
||||
const handler = function() {
|
||||
func.apply(null, args)
|
||||
}
|
||||
timeout = setTimeout(handler, 300)
|
||||
}
|
||||
}
|
||||
|
||||
const search = new GraphTextSearch(graph, cy)
|
||||
document.getElementById("searchField").addEventListener("input", debounce(function (event) {
|
||||
search.searchNodes(event.target.value.trim())
|
||||
}))
|
||||
|
||||
new GraphHighlighter(cy, search)
|
||||
}
|
||||
|
||||
function buildTooltip(lines) {
|
||||
let result = ""
|
||||
for (const line of lines) {
|
||||
if (line.main) {
|
||||
result += `<span style="user-select: text">${line.name}</span>`
|
||||
}
|
||||
else {
|
||||
result += `<br/>${line.name}`
|
||||
}
|
||||
const valueStyleClass = line.selectable ? "tooltipSelectableValue" : (line.main ? "tooltipMainValue" : "tooltipValue")
|
||||
if (line.value != null) {
|
||||
result += `<span class="${valueStyleClass}"`
|
||||
if (line.extraStyle != null && line.extraStyle.length > 0) {
|
||||
result += ` style="${line.extraStyle}"`
|
||||
}
|
||||
if (line.hint != null && line.hint.length !== 0) {
|
||||
result += ` title="${line.hint}"`
|
||||
}
|
||||
result += `>${line.value}</span>`
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
class GraphHighlighter {
|
||||
constructor(cy, graphTextSearch) {
|
||||
this.cy = cy
|
||||
this.graphTextSearch = graphTextSearch
|
||||
|
||||
cy.on("mouseover", "node", e => {
|
||||
this.selectNode(e.target)
|
||||
})
|
||||
cy.on("mouseout", "node", e => {
|
||||
this.deselectNode(e.target)
|
||||
})
|
||||
}
|
||||
|
||||
selectNode(selection) {
|
||||
const cy = this.cy
|
||||
|
||||
if (!this.graphTextSearch.totalUnion.empty()) {
|
||||
cy.elements().difference(this.graphTextSearch.totalUnion).removeClass("semiTransparent")
|
||||
}
|
||||
|
||||
const toHighlight = selection.outgoers().union(selection.incomers()).union(selection)
|
||||
cy.elements().difference(toHighlight).difference(this.graphTextSearch.totalUnion).addClass("semiTransparent")
|
||||
toHighlight.difference(this.graphTextSearch.totalUnion).addClass("highlight")
|
||||
}
|
||||
|
||||
deselectNode(selection) {
|
||||
const cy = this.cy
|
||||
cy.elements().removeClass("semiTransparent")
|
||||
if (!this.graphTextSearch.totalUnion.empty()) {
|
||||
cy.elements().difference(this.graphTextSearch.totalUnion).addClass("semiTransparent")
|
||||
}
|
||||
|
||||
selection.outgoers().union(selection.incomers()).union(selection).difference(this.graphTextSearch.totalUnion).removeClass("highlight")
|
||||
}
|
||||
}
|
||||
|
||||
class GraphTextSearch {
|
||||
constructor(graph, cy) {
|
||||
this.cy = cy
|
||||
this.selectedNodes = new Set()
|
||||
this.totalUnion = cy.collection()
|
||||
|
||||
this.index = new FlexSearch({
|
||||
tokenize: "strict",
|
||||
depth: 3,
|
||||
doc: {
|
||||
id: "data:id",
|
||||
field: [
|
||||
"data:name",
|
||||
"data:pluginId",
|
||||
"data:sourceModule",
|
||||
"data:package",
|
||||
]
|
||||
}
|
||||
})
|
||||
this.index.add(graph)
|
||||
}
|
||||
|
||||
searchNodes(text) {
|
||||
const {selectedNodes, cy, index} = this
|
||||
|
||||
const newNodes = []
|
||||
if (text.length !== 0) {
|
||||
for (const item of index.search(text)) {
|
||||
const node = cy.getElementById(item.data.id)
|
||||
if (node == null) {
|
||||
console.error(`Cannot find node by id ${item.data.id}`)
|
||||
}
|
||||
selectedNodes.delete(node)
|
||||
newNodes.push(node)
|
||||
}
|
||||
}
|
||||
|
||||
cy.elements().removeClass("semiTransparent")
|
||||
for (const prevNode of selectedNodes) {
|
||||
prevNode.removeClass(["highlight", "found"])
|
||||
prevNode.outgoers().union(prevNode.incomers()).removeClass("highlight")
|
||||
}
|
||||
|
||||
selectedNodes.clear()
|
||||
if (newNodes.length === 0) {
|
||||
this.totalUnion = cy.collection()
|
||||
return
|
||||
}
|
||||
|
||||
let totalUnion = null
|
||||
|
||||
for (const newNode of newNodes) {
|
||||
selectedNodes.add(newNode)
|
||||
|
||||
const union = newNode.outgoers().union(newNode.incomers())
|
||||
totalUnion = totalUnion == null ? union : totalUnion.union(union)
|
||||
totalUnion = totalUnion.union(newNode)
|
||||
|
||||
newNode.addClass("found")
|
||||
}
|
||||
|
||||
cy.elements().difference(totalUnion).addClass("semiTransparent")
|
||||
totalUnion.addClass("highlight")
|
||||
this.totalUnion = totalUnion
|
||||
|
||||
cy.animate({
|
||||
// pan: totalUnion.boundingBox(),
|
||||
// center: {eles: totalUnion},
|
||||
fit: {eles: totalUnion},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function shortenPath(p) {
|
||||
const prefix = "plugins/"
|
||||
if (p.startsWith(prefix)) {
|
||||
p = p.substring(prefix.length)
|
||||
}
|
||||
return p
|
||||
.replace("/resources/META-INF/", " ")
|
||||
.replace("/src/main/resources/", " ")
|
||||
.replace("/META-INF/", " ")
|
||||
.replace("/resources/", " ")
|
||||
.replace("/java/src/main/", " ")
|
||||
.replace("/src/main/", " ")
|
||||
.replace("/src/", " ")
|
||||
}
|
||||
20
docs/plugin-graph/index.html
Normal file
20
docs/plugin-graph/index.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Plugin Graph</title>
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
|
||||
<script type="module" src="/main.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="searchFieldContainer">
|
||||
<!--suppress HtmlFormInputWithoutLabel -->
|
||||
<input id="searchField" type="search" placeholder="filter"
|
||||
spellcheck="false"
|
||||
autofocus/>
|
||||
</div>
|
||||
<div id="tooltip"></div>
|
||||
<div id="cy"></div>
|
||||
</body>
|
||||
</html>
|
||||
151
docs/plugin-graph/main.js
Normal file
151
docs/plugin-graph/main.js
Normal file
@@ -0,0 +1,151 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
import "@fontsource/jetbrains-mono/400.css"
|
||||
import "@fontsource/jetbrains-mono/600.css"
|
||||
|
||||
import "./style.css"
|
||||
|
||||
import graphData from "./plugin-graph.json"
|
||||
import cytoscape from "cytoscape"
|
||||
// noinspection SpellCheckingInspection
|
||||
import fCose from "cytoscape-fcose"
|
||||
|
||||
import {GraphTextSearch} from "./src/GraphTextSearch"
|
||||
import {GraphHighlighter} from "./src/GraphHighlighter"
|
||||
import {NodeTooltipManager} from "./src/NodeTooltipManager"
|
||||
|
||||
import popper from "cytoscape-popper"
|
||||
|
||||
cytoscape.use(fCose)
|
||||
cytoscape.use(popper)
|
||||
|
||||
function listener() {
|
||||
document.fonts.load("13px 'JetBrains Mono'", "a").then(function () {
|
||||
initGraph(graphData)
|
||||
})
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
window.addEventListener("DOMContentLoaded", listener)
|
||||
}
|
||||
else {
|
||||
listener()
|
||||
}
|
||||
|
||||
function getItemSizeStyle(factor) {
|
||||
const baseNodeDiameter = 10
|
||||
const size = `${Math.ceil(baseNodeDiameter * factor)}px`
|
||||
return {
|
||||
"width": size,
|
||||
"height": size,
|
||||
}
|
||||
}
|
||||
|
||||
function initGraph(graph) {
|
||||
// noinspection SpellCheckingInspection
|
||||
const layoutOptions = {
|
||||
name: "fcose",
|
||||
quality: "proof",
|
||||
randomize: false,
|
||||
nodeDimensionsIncludeLabels: true,
|
||||
}
|
||||
|
||||
// noinspection SpellCheckingInspection
|
||||
const cy = cytoscape({
|
||||
container: document.getElementById("cy"),
|
||||
elements: graph,
|
||||
layout: layoutOptions,
|
||||
minZoom: 0.4,
|
||||
maxZoom: 3,
|
||||
autoungrabify: true,
|
||||
|
||||
style: [
|
||||
{
|
||||
selector: "node",
|
||||
style: {
|
||||
"label": "data(n)",
|
||||
"font-family": "JetBrains Mono",
|
||||
"font-size": 13,
|
||||
"color": "#515151",
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: "node[type=0]",
|
||||
style: getItemSizeStyle(1),
|
||||
},
|
||||
{
|
||||
selector: "node[type=1]",
|
||||
style: getItemSizeStyle(1.2),
|
||||
},
|
||||
{
|
||||
selector: "node[type=2]",
|
||||
style: getItemSizeStyle(1.4),
|
||||
},
|
||||
{
|
||||
selector: "edge",
|
||||
style: {
|
||||
"curve-style": "straight",
|
||||
"width": 1,
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: "edge[type=0]",
|
||||
style: {
|
||||
"target-arrow-shape": "triangle-backcurve",
|
||||
"arrow-scale": 0.8,
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: "edge[type=1]",
|
||||
style: {
|
||||
"target-arrow-shape": "square",
|
||||
// square is too big
|
||||
"arrow-scale": 0.5,
|
||||
},
|
||||
},
|
||||
|
||||
// highlighting (https://stackoverflow.com/a/38468892)
|
||||
{
|
||||
selector: "node.semiTransparent",
|
||||
style: {"opacity": "0.5"}
|
||||
},
|
||||
{
|
||||
selector: "edge.highlight",
|
||||
style: {"mid-target-arrow-color": "#FFF"}
|
||||
},
|
||||
{
|
||||
selector: "edge.semiTransparent",
|
||||
style: {"opacity": "0.2"}
|
||||
},
|
||||
|
||||
{
|
||||
selector: "node.found",
|
||||
style: {
|
||||
"font-weight": "600",
|
||||
},
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
// ensure that dragging of element causes panning and not selecting
|
||||
// https://github.com/cytoscape/cytoscape.js/issues/1905
|
||||
cy.elements().panify()
|
||||
|
||||
function debounce(func) {
|
||||
let timeout
|
||||
return function (...args) {
|
||||
clearTimeout(timeout)
|
||||
const handler = function() {
|
||||
func.apply(null, args)
|
||||
}
|
||||
timeout = setTimeout(handler, 300)
|
||||
}
|
||||
}
|
||||
|
||||
const search = new GraphTextSearch(graph, cy)
|
||||
document.getElementById("searchField").addEventListener("input", debounce(function (event) {
|
||||
search.searchNodes(event.target.value.trim())
|
||||
}))
|
||||
|
||||
new GraphHighlighter(cy, search)
|
||||
new NodeTooltipManager(cy)
|
||||
}
|
||||
20
docs/plugin-graph/package.json
Normal file
20
docs/plugin-graph/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build --outDir ../out/plugin-graph --base /plugin-graph/ --emptyOutDir",
|
||||
"serve": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vite": "^2.3.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource/jetbrains-mono": "^4.4.2",
|
||||
"cytoscape": "^3.19.0",
|
||||
"cytoscape-fcose": "^2.0.0",
|
||||
"cytoscape-popper": "^2.0.0",
|
||||
"flexsearch": "^0.6.32",
|
||||
"tippy.js": "^6.3.1"
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
171
docs/plugin-graph/pnpm-lock.yaml
generated
Normal file
171
docs/plugin-graph/pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,171 @@
|
||||
lockfileVersion: 5.3
|
||||
|
||||
specifiers:
|
||||
'@fontsource/jetbrains-mono': ^4.4.2
|
||||
cytoscape: ^3.19.0
|
||||
cytoscape-fcose: ^2.0.0
|
||||
cytoscape-popper: ^2.0.0
|
||||
flexsearch: ^0.6.32
|
||||
tippy.js: ^6.3.1
|
||||
vite: ^2.3.6
|
||||
|
||||
dependencies:
|
||||
'@fontsource/jetbrains-mono': 4.4.2
|
||||
cytoscape: 3.19.0
|
||||
cytoscape-fcose: 2.0.0_cytoscape@3.19.0
|
||||
cytoscape-popper: 2.0.0_cytoscape@3.19.0
|
||||
flexsearch: 0.6.32
|
||||
tippy.js: 6.3.1
|
||||
|
||||
devDependencies:
|
||||
vite: 2.3.6
|
||||
|
||||
packages:
|
||||
|
||||
/@fontsource/jetbrains-mono/4.4.2:
|
||||
resolution: {integrity: sha512-5kDjpcnQFogrgGulmES5xX80cDd628+69zSE0ClTqzOAYI43yTyH3MeAH4mxohN6MR7v7AQ9bn+OOFvUuuYwBA==}
|
||||
dev: false
|
||||
|
||||
/@popperjs/core/2.9.2:
|
||||
resolution: {integrity: sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==}
|
||||
dev: false
|
||||
|
||||
/colorette/1.2.2:
|
||||
resolution: {integrity: sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==}
|
||||
dev: true
|
||||
|
||||
/cose-base/2.0.0:
|
||||
resolution: {integrity: sha512-SdE/oR+5SmdxI5lflXiD9RsUfJ78bXbAzpKkMuK890wVa2PTHQGl1pVwpNca81PWQArM4lNdYmLOt1gCyOdfbg==}
|
||||
dependencies:
|
||||
layout-base: 2.0.0
|
||||
dev: false
|
||||
|
||||
/cytoscape-fcose/2.0.0_cytoscape@3.19.0:
|
||||
resolution: {integrity: sha512-Wy80mbn50qKba5KH2GhZRPZYZGvGXCVK3yWcxSxrZOMJwXmoFQqp76OLUASgdSk5jPsrH3XA+O/bW+hJ5I0Dzw==}
|
||||
peerDependencies:
|
||||
cytoscape: ^3.2.0
|
||||
dependencies:
|
||||
cose-base: 2.0.0
|
||||
cytoscape: 3.19.0
|
||||
dev: false
|
||||
|
||||
/cytoscape-popper/2.0.0_cytoscape@3.19.0:
|
||||
resolution: {integrity: sha512-b7WSOn8qXHWtdIXFNmrgc8qkaOs16tMY0EwtRXlxzvn8X+al6TAFrUwZoYATkYSlotfd/36ZMoeKMEoUck6feA==}
|
||||
peerDependencies:
|
||||
cytoscape: ^3.2.0
|
||||
dependencies:
|
||||
'@popperjs/core': 2.9.2
|
||||
cytoscape: 3.19.0
|
||||
dev: false
|
||||
|
||||
/cytoscape/3.19.0:
|
||||
resolution: {integrity: sha512-DANM8bM4EuSTS3DraPK0nLSmzANrzQ2g/cb3u5Biexu/NVsJA+xAoXVvWHRIHDXz3y0gpMjTPv3Z13ZbkNrEPw==}
|
||||
engines: {node: '>=0.10'}
|
||||
dependencies:
|
||||
heap: 0.2.6
|
||||
lodash.debounce: 4.0.8
|
||||
dev: false
|
||||
|
||||
/esbuild/0.12.5:
|
||||
resolution: {integrity: sha512-vcuP53pA5XiwUU4FnlXM+2PnVjTfHGthM7uP1gtp+9yfheGvFFbq/KyuESThmtoHPUrfZH5JpxGVJIFDVD1Egw==}
|
||||
hasBin: true
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
|
||||
/flexsearch/0.6.32:
|
||||
resolution: {integrity: sha512-EF1BWkhwoeLtbIlDbY/vDSLBen/E5l/f1Vg7iX5CDymQCamcx1vhlc3tIZxIDplPjgi0jhG37c67idFbjg+v+Q==}
|
||||
dev: false
|
||||
|
||||
/fsevents/2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/function-bind/1.1.1:
|
||||
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
|
||||
dev: true
|
||||
|
||||
/has/1.0.3:
|
||||
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
|
||||
engines: {node: '>= 0.4.0'}
|
||||
dependencies:
|
||||
function-bind: 1.1.1
|
||||
dev: true
|
||||
|
||||
/heap/0.2.6:
|
||||
resolution: {integrity: sha1-CH4fELBGky/IWU3Z5tN4r8nR5aw=}
|
||||
dev: false
|
||||
|
||||
/is-core-module/2.4.0:
|
||||
resolution: {integrity: sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==}
|
||||
dependencies:
|
||||
has: 1.0.3
|
||||
dev: true
|
||||
|
||||
/layout-base/2.0.0:
|
||||
resolution: {integrity: sha512-I3y9zwMl7/ZaGCeQMUwBVABQ0Vz5c1DFlmMtcON4H/E1sBx+RFOcd7SH8nmkOZ0L2wfVPr68MXvtTZB93vT+2w==}
|
||||
dev: false
|
||||
|
||||
/lodash.debounce/4.0.8:
|
||||
resolution: {integrity: sha1-gteb/zCmfEAF/9XiUVMArZyk168=}
|
||||
dev: false
|
||||
|
||||
/nanoid/3.1.23:
|
||||
resolution: {integrity: sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/path-parse/1.0.7:
|
||||
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
|
||||
dev: true
|
||||
|
||||
/postcss/8.3.0:
|
||||
resolution: {integrity: sha512-+ogXpdAjWGa+fdYY5BQ96V/6tAo+TdSSIMP5huJBIygdWwKtVoB5JWZ7yUd4xZ8r+8Kvvx4nyg/PQ071H4UtcQ==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
dependencies:
|
||||
colorette: 1.2.2
|
||||
nanoid: 3.1.23
|
||||
source-map-js: 0.6.2
|
||||
dev: true
|
||||
|
||||
/resolve/1.20.0:
|
||||
resolution: {integrity: sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==}
|
||||
dependencies:
|
||||
is-core-module: 2.4.0
|
||||
path-parse: 1.0.7
|
||||
dev: true
|
||||
|
||||
/rollup/2.50.6:
|
||||
resolution: {integrity: sha512-6c5CJPLVgo0iNaZWWliNu1Kl43tjP9LZcp6D/tkf2eLH2a9/WeHxg9vfTFl8QV/2SOyaJX37CEm9XuGM0rviUg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
hasBin: true
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
|
||||
/source-map-js/0.6.2:
|
||||
resolution: {integrity: sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/tippy.js/6.3.1:
|
||||
resolution: {integrity: sha512-JnFncCq+rF1dTURupoJ4yPie5Cof978inW6/4S6kmWV7LL9YOSEVMifED3KdrVPEG+Z/TFH2CDNJcQEfaeuQww==}
|
||||
dependencies:
|
||||
'@popperjs/core': 2.9.2
|
||||
dev: false
|
||||
|
||||
/vite/2.3.6:
|
||||
resolution: {integrity: sha512-fsEpNKDHgh3Sn66JH06ZnUBnIgUVUtw6ucDhlOj1CEqxIkymU25yv1/kWDPlIjyYHnalr0cN6V+zzUJ+fmWHYw==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
esbuild: 0.12.5
|
||||
postcss: 8.3.0
|
||||
resolve: 1.20.0
|
||||
rollup: 2.50.6
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
36
docs/plugin-graph/src/GraphHighlighter.js
Normal file
36
docs/plugin-graph/src/GraphHighlighter.js
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
export class GraphHighlighter {
|
||||
constructor(cy, graphTextSearch) {
|
||||
this.cy = cy
|
||||
this.graphTextSearch = graphTextSearch
|
||||
|
||||
cy.on("mouseover", "node", e => {
|
||||
this.selectNode(e.target)
|
||||
})
|
||||
cy.on("mouseout", "node", e => {
|
||||
this.deselectNode(e.target)
|
||||
})
|
||||
}
|
||||
|
||||
selectNode(selection) {
|
||||
const cy = this.cy
|
||||
|
||||
if (!this.graphTextSearch.totalUnion.empty()) {
|
||||
cy.elements().difference(this.graphTextSearch.totalUnion).removeClass("semiTransparent")
|
||||
}
|
||||
|
||||
const toHighlight = selection.outgoers().union(selection.incomers()).union(selection)
|
||||
cy.elements().difference(toHighlight).difference(this.graphTextSearch.totalUnion).addClass("semiTransparent")
|
||||
toHighlight.difference(this.graphTextSearch.totalUnion).addClass("highlight")
|
||||
}
|
||||
|
||||
deselectNode(selection) {
|
||||
const cy = this.cy
|
||||
cy.elements().removeClass("semiTransparent")
|
||||
if (!this.graphTextSearch.totalUnion.empty()) {
|
||||
cy.elements().difference(this.graphTextSearch.totalUnion).addClass("semiTransparent")
|
||||
}
|
||||
|
||||
selection.outgoers().union(selection.incomers()).union(selection).difference(this.graphTextSearch.totalUnion).removeClass("highlight")
|
||||
}
|
||||
}
|
||||
75
docs/plugin-graph/src/GraphTextSearch.js
Normal file
75
docs/plugin-graph/src/GraphTextSearch.js
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
import FlexSearch from "flexsearch"
|
||||
|
||||
export class GraphTextSearch {
|
||||
constructor(graph, cy) {
|
||||
this.cy = cy
|
||||
this.selectedNodes = new Set()
|
||||
this.totalUnion = cy.collection()
|
||||
|
||||
this.index = new FlexSearch({
|
||||
tokenize: "strict",
|
||||
depth: 3,
|
||||
doc: {
|
||||
id: "data:id",
|
||||
field: [
|
||||
"data:name",
|
||||
"data:pluginId",
|
||||
"data:sourceModule",
|
||||
"data:package",
|
||||
]
|
||||
}
|
||||
})
|
||||
this.index.add(graph)
|
||||
}
|
||||
|
||||
searchNodes(text) {
|
||||
const {selectedNodes, cy, index} = this
|
||||
|
||||
const newNodes = []
|
||||
if (text.length !== 0) {
|
||||
for (const item of index.search(text)) {
|
||||
const node = cy.getElementById(item.data.id)
|
||||
if (node == null) {
|
||||
console.error(`Cannot find node by id ${item.data.id}`)
|
||||
}
|
||||
selectedNodes.delete(node)
|
||||
newNodes.push(node)
|
||||
}
|
||||
}
|
||||
|
||||
cy.elements().removeClass("semiTransparent")
|
||||
for (const prevNode of selectedNodes) {
|
||||
prevNode.removeClass(["highlight", "found"])
|
||||
prevNode.outgoers().union(prevNode.incomers()).removeClass("highlight")
|
||||
}
|
||||
|
||||
selectedNodes.clear()
|
||||
if (newNodes.length === 0) {
|
||||
this.totalUnion = cy.collection()
|
||||
return
|
||||
}
|
||||
|
||||
let totalUnion = null
|
||||
|
||||
for (const newNode of newNodes) {
|
||||
selectedNodes.add(newNode)
|
||||
|
||||
const union = newNode.outgoers().union(newNode.incomers())
|
||||
totalUnion = totalUnion == null ? union : totalUnion.union(union)
|
||||
totalUnion = totalUnion.union(newNode)
|
||||
|
||||
newNode.addClass("found")
|
||||
}
|
||||
|
||||
cy.elements().difference(totalUnion).addClass("semiTransparent")
|
||||
totalUnion.addClass("highlight")
|
||||
this.totalUnion = totalUnion
|
||||
|
||||
cy.animate({
|
||||
// pan: totalUnion.boundingBox(),
|
||||
// center: {eles: totalUnion},
|
||||
fit: {eles: totalUnion},
|
||||
})
|
||||
}
|
||||
}
|
||||
90
docs/plugin-graph/src/NodeTooltipManager.js
Normal file
90
docs/plugin-graph/src/NodeTooltipManager.js
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
import Tippy from "tippy.js"
|
||||
|
||||
export class NodeTooltipManager {
|
||||
constructor(cy) {
|
||||
this.tippy = null
|
||||
cy.on("tap", "node", function (event) {
|
||||
const node = event.target
|
||||
const ref = node.popperRef()
|
||||
|
||||
if (this.tippy == null) {
|
||||
const host = document.getElementById("tooltip")
|
||||
this.tippy = new Tippy(host, {
|
||||
getReferenceClientRect: ref.getBoundingClientRect,
|
||||
trigger: "manual",
|
||||
appendTo: host,
|
||||
interactive: true,
|
||||
allowHTML: true,
|
||||
content: buildTooltipContent(node.data()),
|
||||
})
|
||||
}
|
||||
else {
|
||||
this.tippy.setProps({
|
||||
getReferenceClientRect: ref.getBoundingClientRect,
|
||||
content: buildTooltipContent(node.data()),
|
||||
})
|
||||
}
|
||||
|
||||
this.tippy.show()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function buildTooltipContent(item) {
|
||||
const isPackageSet = item.package != null && item.package.length !== 0
|
||||
|
||||
// for us is very important to understand dependencies between source modules, that's why on grap source module name is used
|
||||
// for plugins as node name
|
||||
const lines = [
|
||||
{name: item.name, value: null, main: true},
|
||||
{name: "package", value: isPackageSet ? item.package : "not set", extraStyle: isPackageSet ? null : "color: orange"},
|
||||
]
|
||||
if (item.pluginId !== undefined) {
|
||||
lines.push({name: "pluginId", value: item.pluginId})
|
||||
}
|
||||
lines.push(
|
||||
{name: "sourceModule", value: item.sourceModule},
|
||||
{name: "descriptor", value: shortenPath(item.descriptor), hint: item.descriptor},
|
||||
)
|
||||
return buildTooltip(lines)
|
||||
}
|
||||
|
||||
function buildTooltip(lines) {
|
||||
let result = ""
|
||||
for (const line of lines) {
|
||||
if (line.main) {
|
||||
result += `<span class="tooltipMainName">${line.name}</span>`
|
||||
}
|
||||
else {
|
||||
result += `<br/><span style="user-select: none">${line.name}</span>`
|
||||
}
|
||||
const valueStyleClass = "tooltipValue"
|
||||
if (line.value != null) {
|
||||
result += `<span class="${valueStyleClass}"`
|
||||
if (line.extraStyle != null && line.extraStyle.length > 0) {
|
||||
result += ` style="${line.extraStyle}"`
|
||||
}
|
||||
if (line.hint != null && line.hint.length !== 0) {
|
||||
result += ` title="${line.hint}"`
|
||||
}
|
||||
result += `>${line.value}</span>`
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function shortenPath(p) {
|
||||
const prefix = "plugins/"
|
||||
if (p.startsWith(prefix)) {
|
||||
p = p.substring(prefix.length)
|
||||
}
|
||||
return p
|
||||
.replace("/resources/META-INF/", " ")
|
||||
.replace("/src/main/resources/", " ")
|
||||
.replace("/META-INF/", " ")
|
||||
.replace("/resources/", " ")
|
||||
.replace("/java/src/main/", " ")
|
||||
.replace("/src/main/", " ")
|
||||
.replace("/src/", " ")
|
||||
}
|
||||
48
docs/plugin-graph/style.css
Normal file
48
docs/plugin-graph/style.css
Normal file
@@ -0,0 +1,48 @@
|
||||
/* Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. */
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#cy {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tooltipMainName {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 13px;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.tooltipValue {
|
||||
float: right;
|
||||
margin-left: 10px;
|
||||
user-select: text;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.tippy-box {
|
||||
line-height: 1.2;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
color: #515151;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
|
||||
text-align: justify;
|
||||
border: 1px solid #ebeef5;
|
||||
width: fit-content;
|
||||
max-width: 1000px !important;
|
||||
}
|
||||
|
||||
.searchFieldContainer {
|
||||
position: absolute;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
z-index: 100;
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/android/android-uitests" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/config" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/system" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/docs/out/plugin-graph" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
|
||||
@@ -81,6 +81,7 @@ internal class PluginGraphWriter(private val pluginIdToInfo: Map<String, ModuleI
|
||||
writer.obj("data") {
|
||||
writer.writeStringField("id", id)
|
||||
writer.writeStringField("name", nodeName)
|
||||
writer.writeStringField("n", getShortName(nodeName))
|
||||
writer.writeStringField("package", item.packageName)
|
||||
writer.writeStringField("sourceModule", item.sourceModuleName)
|
||||
writer.writeStringField("descriptor", pathToShortString(item.descriptorFile).replace(File.separatorChar, '/'))
|
||||
@@ -114,6 +115,18 @@ internal class PluginGraphWriter(private val pluginIdToInfo: Map<String, ModuleI
|
||||
dependencyLinks.computeIfAbsent(dependentId) { mutableListOf() }.add(nodeInfoToId.get(dep)!!)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getShortName(name: String): String {
|
||||
if (name.startsWith("intellij.")) {
|
||||
return "i.${name.substring("intellij.".length)}"
|
||||
}
|
||||
else if (name.startsWith("com.intellij.modules.")) {
|
||||
return "c.i.m.${name.substring("com.intellij.modules.".length)}"
|
||||
}
|
||||
else {
|
||||
return name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeLinks(writer: JsonGenerator, links: Map<String, List<String>>, isContent: Boolean) {
|
||||
|
||||
Reference in New Issue
Block a user