Skip to content
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
e5fa904
basic setup
vincerubinetti May 22, 2025
8f4c792
experiment
vincerubinetti May 22, 2025
fc441a0
clamp scale
vincerubinetti May 23, 2025
2181ca8
improve fitting
vincerubinetti May 27, 2025
6bb6b73
add automatic text truncation, better test data
vincerubinetti May 27, 2025
354aaf0
use component wherever possible
vincerubinetti May 27, 2025
a480e99
make upset plot
vincerubinetti May 28, 2025
6257453
make usetheme more robust
vincerubinetti May 29, 2025
66ef0e6
add ability to ignore element in fit calc
vincerubinetti May 29, 2025
8e80344
refactor viewbox
vincerubinetti May 29, 2025
1ed5964
pull out download component
vincerubinetti May 30, 2025
23be73c
refactor to more react idiomatic
vincerubinetti May 30, 2025
bb7d8d0
rearrange util funcs
vincerubinetti Jun 3, 2025
5a3b6b8
fix imports
vincerubinetti Jun 3, 2025
6e2eb97
new chart component
vincerubinetti Jun 4, 2025
c264b4a
fixes
vincerubinetti Jun 4, 2025
008c7da
convert sunburst partially
vincerubinetti Jun 4, 2025
c81680c
svg-ize legend component
vincerubinetti Jun 4, 2025
f6337e8
convert heatmap
vincerubinetti Jun 4, 2025
c6ceb57
network, other fixes
vincerubinetti Jun 5, 2025
6049933
css cleanup
vincerubinetti Jun 5, 2025
1d01e70
convert msa
vincerubinetti Jun 5, 2025
d1f9c38
ipr, fixes
vincerubinetti Jun 5, 2025
b861de6
fix test
vincerubinetti Jun 5, 2025
8cb77a1
small fixes
vincerubinetti Jun 5, 2025
55d0154
incorp tree pr last two commits
vincerubinetti Jun 5, 2025
dc03452
misc cleanup
vincerubinetti Jun 6, 2025
910f56d
upgrade packages
vincerubinetti Jun 6, 2025
2e12d1f
add missing package
vincerubinetti Jun 6, 2025
52a7b78
normalize tree dists
vincerubinetti Jun 6, 2025
da9b62e
aria and misc fixes
vincerubinetti Jun 10, 2025
6d429ab
misc fixes
vincerubinetti Jun 10, 2025
1b451a4
improve hash scrolling
vincerubinetti Jun 10, 2025
f74aaea
improve chart resize, ipr tick spacing
vincerubinetti Jun 10, 2025
8a68041
fix axe test
vincerubinetti Jun 10, 2025
17a0f87
fix tree from rebase
vincerubinetti Jun 10, 2025
e0093ec
get rid of unneeded chart fit group
vincerubinetti Jun 10, 2025
2984c1d
re-add basic ipr scrollbar
vincerubinetti Jun 11, 2025
44eb0da
added seeded random
vincerubinetti Jun 11, 2025
8071521
add useeffect comment
vincerubinetti Jun 11, 2025
39d2e91
ipr tweaks
vincerubinetti Jun 11, 2025
b11acc2
preserve print scroll, remove vestigial print code
vincerubinetti Jun 11, 2025
ed7f70e
fix post print bug size
vincerubinetti Jun 12, 2025
cdcf043
fix seed on netlify deployment
vincerubinetti Jun 12, 2025
f5a6019
add code to preserve url parts consistently
vincerubinetti Jun 12, 2025
4a7f5d5
set pdf filename
vincerubinetti Jun 12, 2025
db7dc0c
fix print bug
vincerubinetti Jun 12, 2025
5daf9d9
add delay to print scroll restore
vincerubinetti Jun 12, 2025
ca3bb05
improve hash preserve behavior
vincerubinetti Jun 12, 2025
63a9b2d
improve scroll to hash behavior
vincerubinetti Jun 12, 2025
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
933 changes: 359 additions & 574 deletions frontend/bun.lock

Large diffs are not rendered by default.

62 changes: 32 additions & 30 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@
"test:e2e": "playwright test",
"test": "bun run test:lint && bun run test:types && bun run test:e2e",
"test:spelling": "bunx cspell src/**/*.{tsx,ts}",
"clean": "rm -rf node_modules dist bun.lockb && bun pm cache rm"
"clean": "rm -rf node_modules dist bun.lock && bun pm cache rm"
},
"dependencies": {
"@headlessui/react": "^2.2.3",
"@radix-ui/react-popover": "^1.1.13",
"@radix-ui/react-tabs": "^1.1.11",
"@radix-ui/react-tooltip": "^1.2.6",
"@reactuses/core": "^6.0.2",
"@tanstack/react-query": "^5.76.1",
"@headlessui/react": "^2.2.4",
"@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-tabs": "^1.1.12",
"@radix-ui/react-tooltip": "^1.2.7",
"@reactuses/core": "^6.0.3",
"@tanstack/react-query": "^5.80.6",
"@tanstack/react-table": "8.21.3",
"clsx": "^2.1.1",
"csv-parse": "^5.6.0",
Expand All @@ -34,53 +34,55 @@
"cytoscape-klay": "^3.1.4",
"cytoscape-spread": "^3.0.0",
"d3": "^7.9.0",
"eslint": "^9.26.0",
"eslint": "^9.28.0",
"html-to-image": "^1.11.13",
"javascript-time-ago": "^2.5.11",
"jotai": "^2.12.4",
"jotai": "^2.12.5",
"lodash": "^4.17.21",
"react": "^19.1.0",
"react-aria-components": "^1.8.0",
"react-aria-components": "^1.10.0",
"react-children-utilities": "^2.10.0",
"react-dom": "^19.1.0",
"react-icons": "^5.5.0",
"react-router": "^7.6.0",
"react-time-ago": "^7.3.3"
"react-router": "^7.6.2",
"react-time-ago": "^7.3.3",
"ua-parser-js": "^2.0.3"
},
"devDependencies": {
"@axe-core/playwright": "^4.10.1",
"@eslint/js": "^9.26.0",
"@ianvs/prettier-plugin-sort-imports": "^4.4.1",
"@axe-core/playwright": "^4.10.2",
"@eslint/js": "^9.28.0",
"@ianvs/prettier-plugin-sort-imports": "^4.4.2",
"@playwright/test": "^1.52.0",
"@types/cytoscape": "^3.21.9",
"@types/cytoscape-avsdf": "^1.0.3",
"@types/cytoscape-dagre": "^2.3.3",
"@types/cytoscape-fcose": "^2.2.4",
"@types/cytoscape-klay": "^3.1.4",
"@types/cytoscape": "^3.21.9",
"@types/d3": "^7.4.3",
"@types/lodash": "^4.17.16",
"@types/node": "^22.15.17",
"@types/react-dom": "^19.1.5",
"@types/react": "^19.1.4",
"@vitejs/plugin-react": "^4.4.1",
"@types/lodash": "^4.17.17",
"@types/node": "^22.15.30",
"@types/react": "^19.1.6",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.5.1",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-prettier": "^5.4.0",
"eslint-plugin-prettier": "^5.4.1",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"get-port": "^7.1.0",
"globals": "^16.1.0",
"lighthouse": "^12.6.0",
"msw": "^2.8.2",
"globals": "^16.2.0",
"lighthouse": "^12.6.1",
"msw": "^2.9.0",
"playwright-lighthouse": "^4.0.0",
"postcss": "^8.5.3",
"postcss": "^8.5.4",
"prettier": "^3.5.3",
"prettier-plugin-css-order": "^2.1.2",
"prettier-plugin-jsdoc": "^1.3.2",
"prettier": "^3.5.3",
"type-fest": "^4.41.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.32.1",
"vite-plugin-svgr": "^4.3.0",
"vite": "^6.3.5"
"typescript-eslint": "^8.33.1",
"vite": "^6.3.5",
"vite-plugin-svgr": "^4.3.0"
},
"msw": {
"workerDirectory": "public"
Expand Down
145 changes: 91 additions & 54 deletions frontend/public/mockServiceWorker.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,23 @@
* Mock Service Worker.
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
*/

const PACKAGE_VERSION = '2.8.2'
const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f'
const PACKAGE_VERSION = '2.9.0'
const INTEGRITY_CHECKSUM = 'f5825c521429caf22a4dd13b66e243af'
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
const activeClientIds = new Set()

self.addEventListener('install', function () {
addEventListener('install', function () {
self.skipWaiting()
})

self.addEventListener('activate', function (event) {
addEventListener('activate', function (event) {
event.waitUntil(self.clients.claim())
})

self.addEventListener('message', async function (event) {
const clientId = event.source.id
addEventListener('message', async function (event) {
const clientId = Reflect.get(event.source || {}, 'id')

if (!clientId || !self.clients) {
return
Expand Down Expand Up @@ -94,17 +93,18 @@ self.addEventListener('message', async function (event) {
}
})

self.addEventListener('fetch', function (event) {
const { request } = event

addEventListener('fetch', function (event) {
// Bypass navigation requests.
if (request.mode === 'navigate') {
if (event.request.mode === 'navigate') {
return
}

// Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests.
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
if (
event.request.cache === 'only-if-cached' &&
event.request.mode !== 'same-origin'
) {
return
}

Expand All @@ -115,48 +115,62 @@ self.addEventListener('fetch', function (event) {
return
}

// Generate unique request ID.
const requestId = crypto.randomUUID()
event.respondWith(handleRequest(event, requestId))
})

/**
* @param {FetchEvent} event
* @param {string} requestId
*/
async function handleRequest(event, requestId) {
const client = await resolveMainClient(event)
const requestCloneForEvents = event.request.clone()
const response = await getResponse(event, client, requestId)

// Send back the response clone for the "response:*" life-cycle events.
// Ensure MSW is active and ready to handle the message, otherwise
// this message will pend indefinitely.
if (client && activeClientIds.has(client.id)) {
;(async function () {
const responseClone = response.clone()

sendToClient(
client,
{
type: 'RESPONSE',
payload: {
requestId,
isMockedResponse: IS_MOCKED_RESPONSE in response,
const serializedRequest = await serializeRequest(requestCloneForEvents)

// Clone the response so both the client and the library could consume it.
const responseClone = response.clone()

sendToClient(
client,
{
type: 'RESPONSE',
payload: {
isMockedResponse: IS_MOCKED_RESPONSE in response,
request: {
id: requestId,
...serializedRequest,
},
response: {
type: responseClone.type,
status: responseClone.status,
statusText: responseClone.statusText,
body: responseClone.body,
headers: Object.fromEntries(responseClone.headers.entries()),
body: responseClone.body,
},
},
[responseClone.body],
)
})()
},
responseClone.body ? [serializedRequest.body, responseClone.body] : [],
)
}

return response
}

// Resolve the main client for the given event.
// Client that issues a request doesn't necessarily equal the client
// that registered the worker. It's with the latter the worker should
// communicate with during the response resolving phase.
/**
* Resolve the main client for the given event.
* Client that issues a request doesn't necessarily equal the client
* that registered the worker. It's with the latter the worker should
* communicate with during the response resolving phase.
* @param {FetchEvent} event
* @returns {Promise<Client | undefined>}
*/
async function resolveMainClient(event) {
const client = await self.clients.get(event.clientId)

Expand Down Expand Up @@ -184,12 +198,16 @@ async function resolveMainClient(event) {
})
}

/**
* @param {FetchEvent} event
* @param {Client | undefined} client
* @param {string} requestId
* @returns {Promise<Response>}
*/
async function getResponse(event, client, requestId) {
const { request } = event

// Clone the request because it might've been already used
// (i.e. its body has been read and sent to the client).
const requestClone = request.clone()
const requestClone = event.request.clone()

function passthrough() {
// Cast the request headers to a new Headers instance
Expand Down Expand Up @@ -230,29 +248,17 @@ async function getResponse(event, client, requestId) {
}

// Notify the client that a request has been intercepted.
const requestBuffer = await request.arrayBuffer()
const serializedRequest = await serializeRequest(event.request)
const clientMessage = await sendToClient(
client,
{
type: 'REQUEST',
payload: {
id: requestId,
url: request.url,
mode: request.mode,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
cache: request.cache,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body: requestBuffer,
keepalive: request.keepalive,
...serializedRequest,
},
},
[requestBuffer],
[serializedRequest.body],
)

switch (clientMessage.type) {
Expand All @@ -268,6 +274,12 @@ async function getResponse(event, client, requestId) {
return passthrough()
}

/**
* @param {Client} client
* @param {any} message
* @param {Array<Transferable>} transferrables
* @returns {Promise<any>}
*/
function sendToClient(client, message, transferrables = []) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel()
Expand All @@ -280,14 +292,18 @@ function sendToClient(client, message, transferrables = []) {
resolve(event.data)
}

client.postMessage(
message,
[channel.port2].concat(transferrables.filter(Boolean)),
)
client.postMessage(message, [
channel.port2,
...transferrables.filter(Boolean),
])
})
}

async function respondWithMock(response) {
/**
* @param {Response} response
* @returns {Response}
*/
function respondWithMock(response) {
// Setting response status code to 0 is a no-op.
// However, when responding with a "Response.error()", the produced Response
// instance will have status code set to 0. Since it's not possible to create
Expand All @@ -305,3 +321,24 @@ async function respondWithMock(response) {

return mockedResponse
}

/**
* @param {Request} request
*/
async function serializeRequest(request) {
return {
url: request.url,
mode: request.mode,
method: request.method,
headers: Object.fromEntries(request.headers.entries()),
cache: request.cache,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body: await request.arrayBuffer(),
keepalive: request.keepalive,
}
}
Loading