From cb300d16b62ca3f3b827d9b8463febe45a2e4ae5 Mon Sep 17 00:00:00 2001 From: Toru Kobayashi Date: Thu, 7 Jul 2022 22:35:03 +0900 Subject: [PATCH] fix: persist state between Cache/History and Network --- .../src/components/NetworkPanel.tsx | 165 +----------------- .../src/components/Panel.tsx | 12 +- .../src/components/SWRDevToolPanel.tsx | 20 ++- .../swr-devtools-panel/src/devtools-cache.ts | 3 +- packages/swr-devtools-panel/src/request.ts | 146 ++++++++++++++++ 5 files changed, 177 insertions(+), 169 deletions(-) create mode 100644 packages/swr-devtools-panel/src/request.ts diff --git a/packages/swr-devtools-panel/src/components/NetworkPanel.tsx b/packages/swr-devtools-panel/src/components/NetworkPanel.tsx index 3f2ce0a..ab31156 100644 --- a/packages/swr-devtools-panel/src/components/NetworkPanel.tsx +++ b/packages/swr-devtools-panel/src/components/NetworkPanel.tsx @@ -1,120 +1,11 @@ -import React, { - MouseEvent, - useCallback, - useLayoutEffect, - useRef, - useState, -} from "react"; +import React, { MouseEvent, useCallback, useState } from "react"; import styled from "styled-components"; import { Cache } from "swr"; +import { EventEmitter, RequestsById, useRequests, useTracks } from "../request"; import { PanelType } from "./SWRDevToolPanel"; import { Timeline } from "./timeline"; -type EventListener = (...args: any[]) => void; -export type EventEmitter = { - subscribe(fn: EventListener): () => void; -}; - -type SWRRequest = { - id: number; - key: string; - type: "success" | "error" | "discarded" | "ongoing"; - startTime: Date; - endTime: Date | null; -}; - -function useRequests(events: EventEmitter) { - const [requestsById, setRequestsById] = useState< - Record - >({}); - const activeRequestsRef = useRef>({}); - - useLayoutEffect(() => { - // Which channel does this request belong to. - const idMap: Record = {}; - - return events.subscribe( - (type, { id, key }: { id: number; key: string }) => { - setRequestsById((currentRequestsByKey) => { - let channelKey = key; - let channelNum = 0; - - let currentRequests; - const activeRequests = activeRequestsRef.current; - - switch (type) { - case "request_start": - // There can be only 1 ongoing request at a time for each channel. Move it to a new one. - while (activeRequests[channelKey]) { - channelKey = `${key}-${++channelNum}`; - } - - currentRequests = currentRequestsByKey[channelKey] || []; - - activeRequests[id] = { - id, - key, - type: "ongoing", - startTime: new Date(), - endTime: null, - }; - activeRequests[channelKey] = activeRequests[id]; - - currentRequests.push(activeRequests[id]); - idMap[id] = channelKey; - - // Always mutate the object. - return { - ...currentRequestsByKey, - [channelKey]: currentRequests, - }; - case "request_success": - if (activeRequests[id]) { - channelKey = idMap[id]; - - activeRequests[id].type = "success"; - activeRequests[id].endTime = new Date(); - delete activeRequests[id]; - delete activeRequests[channelKey]; - - return { ...currentRequestsByKey }; - } - break; - case "request_error": - if (activeRequests[id]) { - channelKey = idMap[id]; - - activeRequests[id].type = "error"; - activeRequests[id].endTime = new Date(); - delete activeRequests[id]; - delete activeRequests[channelKey]; - - return { ...currentRequestsByKey }; - } - break; - case "request_discarded": - if (activeRequests[id]) { - channelKey = idMap[id]; - - activeRequests[id].type = "discarded"; - activeRequests[id].endTime = new Date(); - delete activeRequests[id]; - delete activeRequests[channelKey]; - - return { ...currentRequestsByKey }; - } - break; - } - return currentRequestsByKey; - }); - } - ); - }, [events]); - - return requestsById; -} - function formatTime(time: number, step: number) { if (step >= 500) return time / 1000 + "s"; if (time >= 10000) return time / 1000 + "s"; @@ -122,56 +13,16 @@ function formatTime(time: number, step: number) { } export const NetworkPanel = ({ - cache, - type, - events, + requestsById, + tracks, + startTime, }: { - cache: Cache; - events: EventEmitter; - type: PanelType; + requestsById: RequestsById; + tracks: any[]; + startTime: number; }) => { - const startTime = useState(() => Date.now())[0]; - const requestsById = useRequests(events); const [requestDetail, setRequestDetail] = useState(null); - const tracks = React.useMemo(() => { - const t: any[] = []; - - [...Object.entries(requestsById)].forEach(([channelKey, requests]) => { - const title = requests[0] ? requests[0].key : channelKey; - const shouldMergeKey = requests[0] && channelKey !== requests[0].key; - - t.push({ - key: channelKey, - data: { - title, - shouldMergeKey, - }, - items: requests.map((request) => { - return { - key: request.id, - data: request, - start: request.startTime.getTime(), - end: (request.endTime || new Date()).getTime(), - }; - }), - }); - }); - - t.sort((a, b) => { - if (a.data.title < b.data.title) return -1; - if (a.data.title > b.data.title) return 1; - return 0; - }); - - // Re-assign indexes. - t.forEach((t_, i) => { - t_.data.index = i; - }); - - return t; - }, [requestsById]); - const [trackScale, setTrackScale] = React.useState(5); const [timelineHoverX, setTimelineHoverX] = React.useState(-1); diff --git a/packages/swr-devtools-panel/src/components/Panel.tsx b/packages/swr-devtools-panel/src/components/Panel.tsx index b0b5e82..07d8962 100644 --- a/packages/swr-devtools-panel/src/components/Panel.tsx +++ b/packages/swr-devtools-panel/src/components/Panel.tsx @@ -10,24 +10,20 @@ import { SearchInput } from "./SearchInput"; import { DevToolsCacheData } from "swr-devtools/lib/swr-cache"; export const Panel = ({ - cache, + cacheData, type, selectedItemKey, onSelectItem, }: { - cache: Cache; + cacheData: DevToolsCacheData[]; type: PanelType; selectedItemKey: DevToolsCacheData | null; onSelectItem: (devToolsCacheData: DevToolsCacheData) => void; }) => { - const [currentDevToolsCacheData, historyDevToolsCacheData] = - useDevToolsCache(cache); const [filterText, setFilterText] = useState(""); - const allDevToolsCacheData = - type === "history" ? historyDevToolsCacheData : currentDevToolsCacheData; const selectedDevToolsCacheData = selectedItemKey && - allDevToolsCacheData.find( + cacheData.find( (c) => c.key === selectedItemKey.key && (type === "current" || c.timestamp === selectedItemKey.timestamp) @@ -40,7 +36,7 @@ export const Panel = ({ onChange={(text: string) => setFilterText(text)} /> - {allDevToolsCacheData + {cacheData .filter(({ key }) => filterText === "" || key.includes(filterText)) .map((devToolsCacheData) => ( { const [activePanel, setActivePanel] = useState("current"); const [selectedDevToolsCacheData, selectDevToolsCacheData] = useState(null); + + const requestsById = useRequests(events); + const tracks = useTracks(requestsById); + const startTime = useState(() => Date.now())[0]; + const [currentCacheData, historyCacheData] = useDevToolsCache(cache); + return ( {/* @ts-expect-error https://github.com/styled-components/styled-components/issues/3738 */} @@ -103,10 +111,16 @@ export const SWRDevToolPanel = ({ cache, events }: Props) => { {cache !== null && events !== null ? ( activePanel === "network" ? ( - + ) : ( { const [cacheData, setCacheData] = useState< [DevToolsCacheData[], DevToolsCacheData[]] >([[], []]); useEffect(() => { + if (cache === null) return; const subscribe = createDevToolsCache(cache); const unsubscribe = subscribe( (key_: string, value_: Partial) => { diff --git a/packages/swr-devtools-panel/src/request.ts b/packages/swr-devtools-panel/src/request.ts new file mode 100644 index 0000000..0b55f3a --- /dev/null +++ b/packages/swr-devtools-panel/src/request.ts @@ -0,0 +1,146 @@ +import { useLayoutEffect, useMemo, useRef, useState } from "react"; + +type EventListener = (...args: any[]) => void; +export type EventEmitter = { + subscribe(fn: EventListener): () => void; +}; + +type SWRRequest = { + id: number; + key: string; + type: "success" | "error" | "discarded" | "ongoing"; + startTime: Date; + endTime: Date | null; +}; + +export type RequestsById = Record; + +export function useRequests(events: EventEmitter | null) { + const [requestsById, setRequestsById] = useState({}); + const activeRequestsRef = useRef>({}); + + useLayoutEffect(() => { + // Which channel does this request belong to. + const idMap: Record = {}; + if (events === null) return; + + return events.subscribe( + (type, { id, key }: { id: number; key: string }) => { + setRequestsById((currentRequestsByKey) => { + let channelKey = key; + let channelNum = 0; + + let currentRequests; + const activeRequests = activeRequestsRef.current; + + switch (type) { + case "request_start": + // There can be only 1 ongoing request at a time for each channel. Move it to a new one. + while (activeRequests[channelKey]) { + channelKey = `${key}-${++channelNum}`; + } + + currentRequests = currentRequestsByKey[channelKey] || []; + + activeRequests[id] = { + id, + key, + type: "ongoing", + startTime: new Date(), + endTime: null, + }; + activeRequests[channelKey] = activeRequests[id]; + + currentRequests.push(activeRequests[id]); + idMap[id] = channelKey; + + // Always mutate the object. + return { + ...currentRequestsByKey, + [channelKey]: currentRequests, + }; + case "request_success": + if (activeRequests[id]) { + channelKey = idMap[id]; + + activeRequests[id].type = "success"; + activeRequests[id].endTime = new Date(); + delete activeRequests[id]; + delete activeRequests[channelKey]; + + return { ...currentRequestsByKey }; + } + break; + case "request_error": + if (activeRequests[id]) { + channelKey = idMap[id]; + + activeRequests[id].type = "error"; + activeRequests[id].endTime = new Date(); + delete activeRequests[id]; + delete activeRequests[channelKey]; + + return { ...currentRequestsByKey }; + } + break; + case "request_discarded": + if (activeRequests[id]) { + channelKey = idMap[id]; + + activeRequests[id].type = "discarded"; + activeRequests[id].endTime = new Date(); + delete activeRequests[id]; + delete activeRequests[channelKey]; + + return { ...currentRequestsByKey }; + } + break; + } + return currentRequestsByKey; + }); + } + ); + }, [events]); + + return requestsById; +} + +export const useTracks = (requestsById: RequestsById) => { + return useMemo(() => { + const t: any[] = []; + + [...Object.entries(requestsById)].forEach(([channelKey, requests]) => { + const title = requests[0] ? requests[0].key : channelKey; + const shouldMergeKey = requests[0] && channelKey !== requests[0].key; + + t.push({ + key: channelKey, + data: { + title, + shouldMergeKey, + }, + items: requests.map((request) => { + return { + key: request.id, + data: request, + start: request.startTime.getTime(), + end: (request.endTime || new Date()).getTime(), + }; + }), + }); + }); + + t.sort((a, b) => { + if (a.data.title < b.data.title) return -1; + if (a.data.title > b.data.title) return 1; + return 0; + }); + + // Re-assign indexes. + t.forEach((t_, i) => { + t_.data.index = i; + }); + + return t; + }, [requestsById]); +};