plugin graph viewer - extract search to a web worker

GitOrigin-RevId: 3bb8e871699396720fa978991e9f95ef16afdf58
This commit is contained in:
Vladimir Krivosheev
2021-06-05 10:21:59 +02:00
committed by intellij-monorepo-bot
parent 716c719bb6
commit 3aab104f68
12 changed files with 345 additions and 102 deletions

2
docs/.gitignore vendored
View File

@@ -1,4 +1,4 @@
out/plugin-graph
dist
plugin-graph/node_modules
plugin-graph/plugin-graph.local.json
.DS_Store

View File

@@ -4,19 +4,23 @@
<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/>
<!--suppress HtmlFormInputWithoutLabel -->
<input id="searchField"
type="search"
style="position: fixed; left: 50%; transform: translate(-50%, 0); z-index: 100;"
placeholder="filter"
spellcheck="false"
autofocus/>
<input id="fileInput"
type="file"
style="position: fixed; right: 0; z-index: 100;"
accept="application/json"/>
<input type="file" id="fileInput" accept="application/json" style="float: right">
</div>
<div id="tooltip"></div>
<div id="cy"></div>
<div id="cy" style="width: 100vw; height: 100vh;"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

View File

@@ -3,18 +3,22 @@
"private": true,
"scripts": {
"dev": "vite",
"build": "vite build --outDir ../out/plugin-graph --base /plugin-graph/ --emptyOutDir",
"build": "vite build --emptyOutDir",
"deploy": "pnpm run build && space hosting publish --public ij plugingraph ./dist",
"serve": "vite preview"
},
"devDependencies": {
"vite": "^2.3.6"
},
"dependencies": {
"@fontsource/jetbrains-mono": "^4.4.2",
"comlink": "^4.3.1",
"cytoscape": "^3.19.0",
"cytoscape-fcose": "^2.0.0",
"cytoscape-popper": "^2.0.0",
"fuse.js": "^6.4.6",
"minisearch": "^3.0.2",
"p-debounce": "^4.0.0",
"tippy.js": "^6.3.1"
},
"devDependencies": {
"sass": "^1.34.1",
"vite": "^2.3.6"
}
}

View File

@@ -2,22 +2,28 @@ lockfileVersion: 5.3
specifiers:
'@fontsource/jetbrains-mono': ^4.4.2
comlink: ^4.3.1
cytoscape: ^3.19.0
cytoscape-fcose: ^2.0.0
cytoscape-popper: ^2.0.0
fuse.js: ^6.4.6
minisearch: ^3.0.2
p-debounce: ^4.0.0
sass: ^1.34.1
tippy.js: ^6.3.1
vite: ^2.3.6
dependencies:
'@fontsource/jetbrains-mono': 4.4.2
comlink: 4.3.1
cytoscape: 3.19.0
cytoscape-fcose: 2.0.0_cytoscape@3.19.0
cytoscape-popper: 2.0.0_cytoscape@3.19.0
fuse.js: 6.4.6
minisearch: 3.0.2
p-debounce: 4.0.0
tippy.js: 6.3.1
devDependencies:
sass: 1.34.1
vite: 2.3.6
packages:
@@ -30,10 +36,49 @@ packages:
resolution: {integrity: sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==}
dev: false
/anymatch/3.1.2:
resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==}
engines: {node: '>= 8'}
dependencies:
normalize-path: 3.0.0
picomatch: 2.3.0
dev: true
/binary-extensions/2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'}
dev: true
/braces/3.0.2:
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
engines: {node: '>=8'}
dependencies:
fill-range: 7.0.1
dev: true
/chokidar/3.5.1:
resolution: {integrity: sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==}
engines: {node: '>= 8.10.0'}
dependencies:
anymatch: 3.1.2
braces: 3.0.2
glob-parent: 5.1.2
is-binary-path: 2.1.0
is-glob: 4.0.1
normalize-path: 3.0.0
readdirp: 3.5.0
optionalDependencies:
fsevents: 2.3.2
dev: true
/colorette/1.2.2:
resolution: {integrity: sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==}
dev: true
/comlink/4.3.1:
resolution: {integrity: sha512-+YbhUdNrpBZggBAHWcgQMLPLH1KDF3wJpeqrCKieWQ8RL7atmgsgTQko1XEBK6PsecfopWNntopJ+ByYG1lRaA==}
dev: false
/cose-base/2.0.0:
resolution: {integrity: sha512-SdE/oR+5SmdxI5lflXiD9RsUfJ78bXbAzpKkMuK890wVa2PTHQGl1pVwpNca81PWQArM4lNdYmLOt1gCyOdfbg==}
dependencies:
@@ -72,6 +117,13 @@ packages:
requiresBuild: true
dev: true
/fill-range/7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
dependencies:
to-regex-range: 5.0.1
dev: true
/fsevents/2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -83,10 +135,12 @@ packages:
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
dev: true
/fuse.js/6.4.6:
resolution: {integrity: sha512-/gYxR/0VpXmWSfZOIPS3rWwU8SHgsRTwWuXhyb2O6s7aRuVtHtxCkR33bNYu3wyLyNx/Wpv0vU7FZy8Vj53VNw==}
engines: {node: '>=10'}
dev: false
/glob-parent/5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
dependencies:
is-glob: 4.0.1
dev: true
/has/1.0.3:
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
@@ -99,12 +153,36 @@ packages:
resolution: {integrity: sha1-CH4fELBGky/IWU3Z5tN4r8nR5aw=}
dev: false
/is-binary-path/2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
dependencies:
binary-extensions: 2.2.0
dev: true
/is-core-module/2.4.0:
resolution: {integrity: sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==}
dependencies:
has: 1.0.3
dev: true
/is-extglob/2.1.1:
resolution: {integrity: sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=}
engines: {node: '>=0.10.0'}
dev: true
/is-glob/4.0.1:
resolution: {integrity: sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==}
engines: {node: '>=0.10.0'}
dependencies:
is-extglob: 2.1.1
dev: true
/is-number/7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
dev: true
/layout-base/2.0.0:
resolution: {integrity: sha512-I3y9zwMl7/ZaGCeQMUwBVABQ0Vz5c1DFlmMtcON4H/E1sBx+RFOcd7SH8nmkOZ0L2wfVPr68MXvtTZB93vT+2w==}
dev: false
@@ -113,16 +191,35 @@ packages:
resolution: {integrity: sha1-gteb/zCmfEAF/9XiUVMArZyk168=}
dev: false
/minisearch/3.0.2:
resolution: {integrity: sha512-7rTrJEzovKNi5LSwiIr5aCfJNNo6Lk4O9HTVzjFTMdp+dSr6UisUnEqdwj4rBgNcAcaWW5ClpXnpgTurv8PGqA==}
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
/normalize-path/3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
dev: true
/p-debounce/4.0.0:
resolution: {integrity: sha512-4Ispi9I9qYGO4lueiLDhe4q4iK5ERK8reLsuzH6BPaXn53EGaua8H66PXIFGrW897hwjXp+pVLrm/DLxN0RF0A==}
engines: {node: '>=12'}
dev: false
/path-parse/1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
dev: true
/picomatch/2.3.0:
resolution: {integrity: sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==}
engines: {node: '>=8.6'}
dev: true
/postcss/8.3.0:
resolution: {integrity: sha512-+ogXpdAjWGa+fdYY5BQ96V/6tAo+TdSSIMP5huJBIygdWwKtVoB5JWZ7yUd4xZ8r+8Kvvx4nyg/PQ071H4UtcQ==}
engines: {node: ^10 || ^12 || >=14}
@@ -132,6 +229,13 @@ packages:
source-map-js: 0.6.2
dev: true
/readdirp/3.5.0:
resolution: {integrity: sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==}
engines: {node: '>=8.10.0'}
dependencies:
picomatch: 2.3.0
dev: true
/resolve/1.20.0:
resolution: {integrity: sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==}
dependencies:
@@ -147,6 +251,14 @@ packages:
fsevents: 2.3.2
dev: true
/sass/1.34.1:
resolution: {integrity: sha512-scLA7EIZM+MmYlej6sdVr0HRbZX5caX5ofDT9asWnUJj21oqgsC+1LuNfm0eg+vM0fCTZHhwImTiCU0sx9h9CQ==}
engines: {node: '>=8.9.0'}
hasBin: true
dependencies:
chokidar: 3.5.1
dev: true
/source-map-js/0.6.2:
resolution: {integrity: sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==}
engines: {node: '>=0.10.0'}
@@ -158,6 +270,13 @@ packages:
'@popperjs/core': 2.9.2
dev: false
/to-regex-range/5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
dependencies:
is-number: 7.0.0
dev: true
/vite/2.3.6:
resolution: {integrity: sha512-fsEpNKDHgh3Sn66JH06ZnUBnIgUVUtw6ucDhlOj1CEqxIkymU25yv1/kWDPlIjyYHnalr0cN6V+zzUJ+fmWHYw==}
engines: {node: '>=12.0.0'}

View File

@@ -1,6 +1,8 @@
// 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 Fuse from "fuse.js"
import {GraphHighlighter} from "./GraphHighlighter"
import {wrap} from "comlink"
// noinspection ES6CheckImport
import SearchWorker from "./SearchWorker?worker"
export class GraphTextSearch {
constructor(graph, cy) {
@@ -8,28 +10,21 @@ export class GraphTextSearch {
this.selectedNodes = new Set()
this.totalUnion = cy.collection()
this.index = new Fuse(graph, {
threshold: 0.0,
ignoreLocation: true,
ignoreFieldNorm: true,
keys: [
{name: "data.name", weight: 4},
{name: "data.pluginId", weight: 3},
{name: "data.sourceModule", weight: 2},
{name: "data.package", weight: 1},
],
})
this.index = wrap(new SearchWorker())
this.index.add(graph
.filter(it => it.group === "nodes" && "type" in it.data /* not a compound node */)
.map(it => it.data))
}
searchNodes(text) {
async 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)
for (const item of await index.search(text)) {
const node = cy.getElementById(item.id)
if (node == null) {
console.error(`Cannot find node by id ${item.data.id}`)
console.error(`Cannot find node by id ${item.id}`)
}
selectedNodes.delete(node)
newNodes.push(node)

View File

@@ -1,9 +1,13 @@
// 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"
import "tippy.js/animations/scale.css"
import cytoscape from "cytoscape"
import popper from "cytoscape-popper"
export class GraphTooltipManager {
constructor(cy) {
cytoscape.use(popper)
this.tippy = null
this.lastNode = null
cy.on("tap", "node", function (event) {
@@ -12,7 +16,7 @@ export class GraphTooltipManager {
if (this.lastNode === node) {
this.lastNode = null
if (this.tippy != null) {
this.tippy.hideWithInteractivity()
this.tippy.hide()
}
return
}

View File

@@ -0,0 +1,16 @@
// 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 {expose} from "comlink"
import MiniSearch from "minisearch"
const index = new MiniSearch({
fields: ["name", "pluginId", "sourceModule", "package"],
})
expose({
add(data) {
index.addAll(data)
},
search(query) {
return index.search(query)
}
})

View File

@@ -1,50 +1,50 @@
// 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 "./style.css"
import graphData from "./plugin-graph.json"
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 {GraphTooltipManager} from "./src/GraphTooltipManager"
import popper from "cytoscape-popper"
import pDebounce from "p-debounce"
cytoscape.use(fCose)
cytoscape.use(popper)
function listener() {
let cy = null
function init() {
Promise.all([
document.fonts.load("11px 'JetBrains Mono'"),
document.fonts.load("13px 'JetBrains Mono'"),
document.fonts.load("bold 13px 'JetBrains Mono'")
]).then(function () {
const fileInput = document.getElementById("fileInput")
fileInput.addEventListener("change", event => {
handleFile(event.target.files[0])
})
// https://stackoverflow.com/a/31205873
document.fonts.load("bold 13px 'JetBrains Mono'")
const dropbox = document.body
dropbox.addEventListener("dragenter", preventDefault);
dropbox.addEventListener("dragover", preventDefault);
dropbox.addEventListener("dragenter", preventDefault)
dropbox.addEventListener("dragover", preventDefault)
dropbox.addEventListener("drop", event => {
event.stopPropagation()
event.preventDefault()
const file = event.dataTransfer.files[0]
const timeFormat = new Intl.DateTimeFormat('default', {
// noinspection JSCheckFunctionSignatures
const timeFormat = new Intl.DateTimeFormat("default", {
timeStyle: "medium",
})
document.title = `${file.name} (${timeFormat.format(new Date())})`
handleFile(file)
});
})
initGraph(graphData)
initGraph(graphData).catch(e => {
console.error("Init failed", e)
})
})
}
@@ -55,17 +55,13 @@ function preventDefault(e) {
function handleFile(selectedFile) {
selectedFile.text().then(it => {
initGraph(JSON.parse(it))
if (cy != null) {
// noinspection JSCheckFunctionSignatures
cy.json(JSON.parse(it))
}
})
}
if (document.readyState === "loading") {
window.addEventListener("DOMContentLoaded", listener)
}
else {
listener()
}
function getItemSizeStyle(factor) {
const baseNodeDiameter = 10
const size = `${Math.ceil(baseNodeDiameter * factor)}px`
@@ -75,7 +71,7 @@ function getItemSizeStyle(factor) {
}
}
function initGraph(graph) {
async function initGraph(graph) {
// noinspection SpellCheckingInspection
const layoutOptions = {
name: "fcose",
@@ -85,7 +81,7 @@ function initGraph(graph) {
}
// noinspection SpellCheckingInspection
const cy = cytoscape({
cy = cytoscape({
container: document.getElementById("cy"),
elements: graph,
layout: layoutOptions,
@@ -180,22 +176,14 @@ function initGraph(graph) {
// 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 (await import("./GraphTextSearch")).GraphTextSearch(graph, cy)
// noinspection JSCheckFunctionSignatures
document.getElementById("searchField").addEventListener("input", pDebounce(function (event) {
return search.searchNodes(event.target.value.trim())
}, 300))
const search = new GraphTextSearch(graph, cy)
document.getElementById("searchField").addEventListener("input", debounce(function (event) {
search.searchNodes(event.target.value.trim())
}))
new (await import("./GraphHighlighter")).GraphHighlighter(cy, search)
new (await import("./GraphTooltipManager")).GraphTooltipManager(cy)
}
new GraphHighlighter(cy, search)
new GraphTooltipManager(cy)
}
init()

View File

@@ -1,11 +1,7 @@
/* 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%;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #515151;
}
.tooltipMainName {
@@ -25,8 +21,6 @@ html, body {
.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;
@@ -35,14 +29,4 @@ html, body {
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;
}

View File

@@ -0,0 +1,129 @@
// 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 default {
plugins: [
{
name: "vite-plugin-fontsource",
apply: "build",
transformIndexHtml(html, context) {
const tags = []
for (const item of Object.values(context.bundle)) {
if (item.type !== "asset" || !item.fileName.endsWith(".woff2")) {
continue
}
tags.push({
tag: "link",
attrs: {
rel: "preload",
href: `/${item.fileName}`,
as: "font",
type: "font/woff2",
crossorigin: true,
},
injectTo: "head",
})
}
return tags
},
}
],
build: {
rollupOptions: {
output: {
manualChunks: {
cytoscape: ["cytoscape", "cytoscape-fcose", "cytoscape/dist/cytoscape.esm.js"]
},
},
}
}
}
function injectFonts({
families,
text,
preconnect = true,
display = 'swap',
}) {
const specs = []
const deferredSpecs = []
const tags = []
for (const family of families) {
if (typeof family === "string") {
deferredSpecs.push(family)
continue
}
const {
name,
styles,
defer = true,
} = family
let spec = name
if (typeof styles === 'string')
spec += `:${styles}`
if (defer)
deferredSpecs.push(spec)
else
specs.push(spec)
}
// warm up the fonts origin
if (preconnect && specs.length + deferredSpecs.length > 0) {
tags.push({
tag: 'link',
attrs: {
rel: 'preconnect',
href: 'https://fonts.gstatic.com/',
crossorigin: true,
},
})
}
// defer loading font-faces definitions
// @see https://web.dev/optimize-lcp/#defer-non-critical-css
if (deferredSpecs.length > 0) {
let href = `${GoogleFontsBase}?family=${deferredSpecs.join('&family=')}`
if (typeof display === 'string' && display !== 'auto')
href += `&display=${display}`
if (typeof text === 'string' && text.length > 0)
href += `&text=${text}`
tags.push({
tag: "link",
attrs: {
rel: "preload",
as: "font",
onload: 'this.rel=\'stylesheet\'',
href,
},
})
}
// load critical fonts
if (specs.length > 0) {
let href = `${GoogleFontsBase}?family=${specs.join('&family=')}`
if (typeof display === 'string' && display !== 'auto')
href += `&display=${display}`
if (typeof text === 'string' && text.length > 0)
href += `&text=${text}`
tags.push({
tag: 'link',
attrs: {
rel: 'stylesheet',
href,
},
})
}
return tags
}

View File

@@ -6,7 +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" />
<excludeFolder url="file://$MODULE_DIR$/docs/plugin-graph/dist" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />

View File

@@ -37,7 +37,7 @@ class PluginModelTest {
val out = Path.of(PlatformTestUtil.getCommunityPath(), "docs/plugin-graph/plugin-graph.local.json")
validator.writeGraph(out)
println("\nGraph is written to $out")
println("Drop file to https://ij-platform-flow.develar.org/plugin-graph to visualize.")
println("Drop file to https://plugingraph.ij.pages.jetbrains.team/ to visualize.")
}
}
}