Merge pull request #3802 from asgerf/asgerf/use-message-from-extension

Add useMessageFromExtension hook
This commit is contained in:
Asger F 2024-11-18 12:47:42 +01:00 коммит произвёл GitHub
Родитель 873d4e3e7e bd38355591
Коммит ea45e389c4
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
8 изменённых файлов: 235 добавлений и 311 удалений

Просмотреть файл

@ -0,0 +1,27 @@
import { useEffect } from "react";
/**
* Invokes the given callback when a message is received from the extension.
*/
export function useMessageFromExtension<T>(
onEvent: (event: T) => void,
onEventDependencies: unknown[],
): void {
useEffect(() => {
const listener = (evt: MessageEvent) => {
if (evt.origin === window.origin) {
onEvent(evt.data as T);
} else {
// sanitize origin
const origin = evt.origin.replace(/\n|\r/g, "");
console.error(`Invalid event origin ${origin}`);
}
};
window.addEventListener("message", listener);
return () => {
window.removeEventListener("message", listener);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, onEventDependencies);
}

Просмотреть файл

@ -1,4 +1,4 @@
import { useState, useEffect, useRef } from "react";
import { useState, useRef } from "react";
import { styled } from "styled-components";
import type {
@ -16,6 +16,7 @@ import CompareTable from "./CompareTable";
import "../results/resultsView.css";
import { assertNever } from "../../common/helpers-pure";
import { useMessageFromExtension } from "../common/useMessageFromExtension";
const Header = styled.div`
display: flex;
@ -50,115 +51,101 @@ export function Compare(_: Record<string, never>): React.JSX.Element {
comparison?.result &&
(comparison.result.to.length || comparison.result.from.length);
useEffect(() => {
const listener = (evt: MessageEvent) => {
if (evt.origin === window.origin) {
const msg: ToCompareViewMessage = evt.data;
switch (msg.t) {
case "setComparisonQueryInfo":
setQueryInfo(msg);
break;
case "setComparisons":
setComparison(msg);
break;
case "streamingComparisonSetup":
setComparison(null);
streamingComparisonRef.current = msg;
break;
case "streamingComparisonAddResults": {
const prev = streamingComparisonRef.current;
if (prev === null) {
console.warn(
'Received "streamingComparisonAddResults" before "streamingComparisonSetup"',
useMessageFromExtension<ToCompareViewMessage>((msg) => {
switch (msg.t) {
case "setComparisonQueryInfo":
setQueryInfo(msg);
break;
case "setComparisons":
setComparison(msg);
break;
case "streamingComparisonSetup":
setComparison(null);
streamingComparisonRef.current = msg;
break;
case "streamingComparisonAddResults": {
const prev = streamingComparisonRef.current;
if (prev === null) {
console.warn(
'Received "streamingComparisonAddResults" before "streamingComparisonSetup"',
);
break;
}
if (prev.id !== msg.id) {
console.warn(
'Received "streamingComparisonAddResults" with different id, ignoring',
);
break;
}
let result: QueryCompareResult;
switch (prev.result.kind) {
case "raw":
if (msg.result.kind !== "raw") {
throw new Error(
"Streaming comparison: expected raw results, got interpreted results",
);
break;
}
if (prev.id !== msg.id) {
console.warn(
'Received "streamingComparisonAddResults" with different id, ignoring',
);
break;
}
let result: QueryCompareResult;
switch (prev.result.kind) {
case "raw":
if (msg.result.kind !== "raw") {
throw new Error(
"Streaming comparison: expected raw results, got interpreted results",
);
}
result = {
...prev.result,
from: [...prev.result.from, ...msg.result.from],
to: [...prev.result.to, ...msg.result.to],
};
break;
case "interpreted":
if (msg.result.kind !== "interpreted") {
throw new Error(
"Streaming comparison: expected interpreted results, got raw results",
);
}
result = {
...prev.result,
from: [...prev.result.from, ...msg.result.from],
to: [...prev.result.to, ...msg.result.to],
};
break;
default:
throw new Error("Unexpected comparison result kind");
}
streamingComparisonRef.current = {
...prev,
result,
result = {
...prev.result,
from: [...prev.result.from, ...msg.result.from],
to: [...prev.result.to, ...msg.result.to],
};
break;
}
case "streamingComparisonComplete":
if (streamingComparisonRef.current === null) {
console.warn(
'Received "streamingComparisonComplete" before "streamingComparisonSetup"',
case "interpreted":
if (msg.result.kind !== "interpreted") {
throw new Error(
"Streaming comparison: expected interpreted results, got raw results",
);
setComparison(null);
break;
}
if (streamingComparisonRef.current.id !== msg.id) {
console.warn(
'Received "streamingComparisonComplete" with different id, ignoring',
);
break;
}
setComparison({
...streamingComparisonRef.current,
t: "setComparisons",
});
streamingComparisonRef.current = null;
break;
case "setUserSettings":
setUserSettings(msg.userSettings);
result = {
...prev.result,
from: [...prev.result.from, ...msg.result.from],
to: [...prev.result.to, ...msg.result.to],
};
break;
default:
assertNever(msg);
throw new Error("Unexpected comparison result kind");
}
} else {
// sanitize origin
const origin = evt.origin.replace(/\n|\r/g, "");
console.error(`Invalid event origin ${origin}`);
}
};
window.addEventListener("message", listener);
return () => {
window.removeEventListener("message", listener);
};
streamingComparisonRef.current = {
...prev,
result,
};
break;
}
case "streamingComparisonComplete":
if (streamingComparisonRef.current === null) {
console.warn(
'Received "streamingComparisonComplete" before "streamingComparisonSetup"',
);
setComparison(null);
break;
}
if (streamingComparisonRef.current.id !== msg.id) {
console.warn(
'Received "streamingComparisonComplete" with different id, ignoring',
);
break;
}
setComparison({
...streamingComparisonRef.current,
t: "setComparisons",
});
streamingComparisonRef.current = null;
break;
case "setUserSettings":
setUserSettings(msg.userSettings);
break;
default:
assertNever(msg);
}
}, []);
if (!queryInfo || !comparison) {

Просмотреть файл

@ -1,7 +1,8 @@
import { useEffect, useState } from "react";
import { useState } from "react";
import type { ToDataFlowPathsMessage } from "../../common/interface-types";
import type { DataFlowPaths as DataFlowPathsDomainModel } from "../../variant-analysis/shared/data-flow-paths";
import { DataFlowPaths } from "./DataFlowPaths";
import { useMessageFromExtension } from "../common/useMessageFromExtension";
export type DataFlowPathsViewProps = {
dataFlowPaths?: DataFlowPathsDomainModel;
@ -14,28 +15,12 @@ export function DataFlowPathsView({
DataFlowPathsDomainModel | undefined
>(initialDataFlowPaths);
useEffect(() => {
const listener = (evt: MessageEvent) => {
if (evt.origin === window.origin) {
const msg: ToDataFlowPathsMessage = evt.data;
if (msg.t === "setDataFlowPaths") {
setDataFlowPaths(msg.dataFlowPaths);
useMessageFromExtension<ToDataFlowPathsMessage>((msg) => {
setDataFlowPaths(msg.dataFlowPaths);
// Scroll to the top of the page when we're rendering
// new data flow paths.
window.scrollTo(0, 0);
}
} else {
// sanitize origin
const origin = evt.origin.replace(/\n|\r/g, "");
console.error(`Invalid event origin ${origin}`);
}
};
window.addEventListener("message", listener);
return () => {
window.removeEventListener("message", listener);
};
// Scroll to the top of the page when we're rendering
// new data flow paths.
window.scrollTo(0, 0);
}, []);
if (!dataFlowPaths) {

Просмотреть файл

@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from "react";
import { useMemo, useState } from "react";
import { MethodModeling } from "./MethodModeling";
import { getModelingStatus } from "../../model-editor/shared/modeling-status";
import type { Method } from "../../model-editor/method";
@ -12,6 +12,7 @@ import { NoMethodSelected } from "./NoMethodSelected";
import type { MethodModelingPanelViewState } from "../../model-editor/shared/view-state";
import { MethodAlreadyModeled } from "./MethodAlreadyModeled";
import { defaultModelConfig } from "../../model-editor/languages";
import { useMessageFromExtension } from "../common/useMessageFromExtension";
type Props = {
initialViewState?: MethodModelingPanelViewState;
@ -36,47 +37,33 @@ export function MethodModelingView({
[modeledMethods, isMethodModified],
);
useEffect(() => {
const listener = (evt: MessageEvent) => {
if (evt.origin === window.origin) {
const msg: ToMethodModelingMessage = evt.data;
switch (msg.t) {
case "setMethodModelingPanelViewState":
setViewState(msg.viewState);
break;
case "setInModelingMode":
setInModelingMode(msg.inModelingMode);
break;
case "setMultipleModeledMethods":
setModeledMethods(msg.modeledMethods);
break;
case "setMethodModified":
setIsMethodModified(msg.isModified);
break;
case "setNoMethodSelected":
setMethod(undefined);
setModeledMethods([]);
setIsMethodModified(false);
break;
case "setSelectedMethod":
setMethod(msg.method);
setModeledMethods(msg.modeledMethods);
setIsMethodModified(msg.isModified);
break;
default:
assertNever(msg);
}
} else {
// sanitize origin
const origin = evt.origin.replace(/\n|\r/g, "");
console.error(`Invalid event origin ${origin}`);
}
};
window.addEventListener("message", listener);
return () => {
window.removeEventListener("message", listener);
};
useMessageFromExtension<ToMethodModelingMessage>((msg) => {
switch (msg.t) {
case "setMethodModelingPanelViewState":
setViewState(msg.viewState);
break;
case "setInModelingMode":
setInModelingMode(msg.inModelingMode);
break;
case "setMultipleModeledMethods":
setModeledMethods(msg.modeledMethods);
break;
case "setMethodModified":
setIsMethodModified(msg.isModified);
break;
case "setNoMethodSelected":
setMethod(undefined);
setModeledMethods([]);
setIsMethodModified(false);
break;
case "setSelectedMethod":
setMethod(msg.method);
setModeledMethods(msg.modeledMethods);
setIsMethodModified(msg.isModified);
break;
default:
assertNever(msg);
}
}, []);
if (!inModelingMode || !viewState?.language) {

Просмотреть файл

@ -1,4 +1,4 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import { useCallback, useMemo, useState } from "react";
import { styled } from "styled-components";
import { ModelAlertsHeader } from "./ModelAlertsHeader";
import type { ModelAlertsViewState } from "../../model-editor/shared/view-state";
@ -18,6 +18,7 @@ import {
} from "../../model-editor/shared/model-alerts-filter-sort";
import type { ModelAlertsFilterSortState } from "../../model-editor/shared/model-alerts-filter-sort";
import type { ModeledMethod } from "../../model-editor/modeled-method";
import { useMessageFromExtension } from "../common/useMessageFromExtension";
type Props = {
initialViewState?: ModelAlertsViewState;
@ -67,47 +68,33 @@ export function ModelAlerts({
null,
);
useEffect(() => {
const listener = (evt: MessageEvent) => {
if (evt.origin === window.origin) {
const msg: ToModelAlertsMessage = evt.data;
switch (msg.t) {
case "setModelAlertsViewState": {
setViewState(msg.viewState);
break;
}
case "setVariantAnalysis": {
setVariantAnalysis(msg.variantAnalysis);
break;
}
case "setRepoResults": {
setRepoResults((oldRepoResults) => {
const newRepoIds = msg.repoResults.map((r) => r.repositoryId);
return [
...oldRepoResults.filter(
(v) => !newRepoIds.includes(v.repositoryId),
),
...msg.repoResults,
];
});
break;
}
case "revealModel": {
setRevealedModel(msg.modeledMethod);
break;
}
}
} else {
// sanitize origin
const origin = evt.origin.replace(/\n|\r/g, "");
console.error(`Invalid event origin ${origin}`);
useMessageFromExtension<ToModelAlertsMessage>((msg) => {
switch (msg.t) {
case "setModelAlertsViewState": {
setViewState(msg.viewState);
break;
}
};
window.addEventListener("message", listener);
return () => {
window.removeEventListener("message", listener);
};
case "setVariantAnalysis": {
setVariantAnalysis(msg.variantAnalysis);
break;
}
case "setRepoResults": {
setRepoResults((oldRepoResults) => {
const newRepoIds = msg.repoResults.map((r) => r.repositoryId);
return [
...oldRepoResults.filter(
(v) => !newRepoIds.includes(v.repositoryId),
),
...msg.repoResults,
];
});
break;
}
case "revealModel": {
setRevealedModel(msg.modeledMethod);
break;
}
}
}, []);
const modelAlerts = useMemo(() => {

Просмотреть файл

@ -21,6 +21,7 @@ import { INITIAL_HIDE_MODELED_METHODS_VALUE } from "../../model-editor/shared/hi
import type { AccessPathSuggestionOptions } from "../../model-editor/suggestions";
import type { ModelEvaluationRunState } from "../../model-editor/shared/model-evaluation-run-state";
import { ModelEvaluation } from "./ModelEvaluation";
import { useMessageFromExtension } from "../common/useMessageFromExtension";
const LoadingContainer = styled.div`
text-align: center;
@ -129,47 +130,33 @@ export function ModelEditor({
AccessPathSuggestionOptions | undefined
>(undefined);
useEffect(() => {
const listener = (evt: MessageEvent) => {
if (evt.origin === window.origin) {
const msg: ToModelEditorMessage = evt.data;
switch (msg.t) {
case "setModelEditorViewState":
setViewState(msg.viewState);
break;
case "setMethods":
setMethods(msg.methods);
break;
case "setModeledAndModifiedMethods":
setModeledMethods(msg.methods);
setModifiedSignatures(new Set(msg.modifiedMethodSignatures));
break;
case "setModifiedMethods":
setModifiedSignatures(new Set(msg.methodSignatures));
break;
case "revealMethod":
setRevealedMethodSignature(msg.methodSignature);
break;
case "setAccessPathSuggestions":
setAccessPathSuggestions(msg.accessPathSuggestions);
break;
case "setModelEvaluationRun":
setEvaluationRun(msg.run);
break;
default:
assertNever(msg);
}
} else {
// sanitize origin
const origin = evt.origin.replace(/\n|\r/g, "");
console.error(`Invalid event origin ${origin}`);
}
};
window.addEventListener("message", listener);
return () => {
window.removeEventListener("message", listener);
};
useMessageFromExtension<ToModelEditorMessage>((msg) => {
switch (msg.t) {
case "setModelEditorViewState":
setViewState(msg.viewState);
break;
case "setMethods":
setMethods(msg.methods);
break;
case "setModeledAndModifiedMethods":
setModeledMethods(msg.methods);
setModifiedSignatures(new Set(msg.modifiedMethodSignatures));
break;
case "setModifiedMethods":
setModifiedSignatures(new Set(msg.methodSignatures));
break;
case "revealMethod":
setRevealedMethodSignature(msg.methodSignature);
break;
case "setAccessPathSuggestions":
setAccessPathSuggestions(msg.accessPathSuggestions);
break;
case "setModelEvaluationRun":
setEvaluationRun(msg.run);
break;
default:
assertNever(msg);
}
}, []);
useEffect(() => {

Просмотреть файл

@ -16,11 +16,12 @@ import {
DEFAULT_USER_SETTINGS,
GRAPH_TABLE_NAME,
} from "../../common/interface-types";
import { useMessageFromExtension } from "../common/useMessageFromExtension";
import { ResultTables } from "./ResultTables";
import { onNavigation } from "./navigation";
import "./resultsView.css";
import { useCallback, useEffect, useState } from "react";
import { useCallback, useState } from "react";
/**
* ResultsApp.tsx
@ -113,8 +114,8 @@ export function ResultsApp() {
[],
);
const handleMessage = useCallback(
(msg: IntoResultsViewMsg): void => {
useMessageFromExtension<IntoResultsViewMsg>(
(msg) => {
switch (msg.t) {
case "setUserSettings":
setUserSettings(msg.userSettings);
@ -189,26 +190,6 @@ export function ResultsApp() {
[updateStateWithNewResultsInfo],
);
const vscodeMessageHandler = useCallback(
(evt: MessageEvent) => {
// sanitize origin
const origin = evt.origin.replace(/\n|\r/g, "");
if (evt.origin === window.origin) {
handleMessage(evt.data as IntoResultsViewMsg);
} else {
console.error(`Invalid event origin ${origin}`);
}
},
[handleMessage],
);
useEffect(() => {
window.addEventListener("message", vscodeMessageHandler);
return () => {
window.removeEventListener("message", vscodeMessageHandler);
};
}, [vscodeMessageHandler]);
const { displayedResults, nextResultsInfo, isExpectingResultsUpdate } = state;
if (
displayedResults.results !== null &&

Просмотреть файл

@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from "react";
import { useCallback, useState } from "react";
import type {
VariantAnalysis as VariantAnalysisDomainModel,
@ -13,6 +13,7 @@ import type { ToVariantAnalysisMessage } from "../../common/interface-types";
import { vscode } from "../vscode-api";
import { defaultFilterSortState } from "../../variant-analysis/shared/variant-analysis-filter-sort";
import { sendTelemetry, useTelemetryOnChange } from "../common/telemetry";
import { useMessageFromExtension } from "../common/useMessageFromExtension";
export type VariantAnalysisProps = {
variantAnalysis?: VariantAnalysisDomainModel;
@ -77,49 +78,31 @@ export function VariantAnalysis({
debounceTimeoutMillis: 1000,
});
useEffect(() => {
const listener = (evt: MessageEvent) => {
if (evt.origin === window.origin) {
const msg: ToVariantAnalysisMessage = evt.data;
if (msg.t === "setVariantAnalysis") {
setVariantAnalysis(msg.variantAnalysis);
vscode.setState({
variantAnalysisId: msg.variantAnalysis.id,
});
} else if (msg.t === "setFilterSortState") {
setFilterSortState(msg.filterSortState);
} else if (msg.t === "setRepoResults") {
setRepoResults((oldRepoResults) => {
const newRepoIds = msg.repoResults.map((r) => r.repositoryId);
return [
...oldRepoResults.filter(
(v) => !newRepoIds.includes(v.repositoryId),
),
...msg.repoResults,
];
});
} else if (msg.t === "setRepoStates") {
setRepoStates((oldRepoStates) => {
const newRepoIds = msg.repoStates.map((r) => r.repositoryId);
return [
...oldRepoStates.filter(
(v) => !newRepoIds.includes(v.repositoryId),
),
...msg.repoStates,
];
});
}
} else {
// sanitize origin
const origin = evt.origin.replace(/\n|\r/g, "");
console.error(`Invalid event origin ${origin}`);
}
};
window.addEventListener("message", listener);
return () => {
window.removeEventListener("message", listener);
};
useMessageFromExtension<ToVariantAnalysisMessage>((msg) => {
if (msg.t === "setVariantAnalysis") {
setVariantAnalysis(msg.variantAnalysis);
vscode.setState({
variantAnalysisId: msg.variantAnalysis.id,
});
} else if (msg.t === "setFilterSortState") {
setFilterSortState(msg.filterSortState);
} else if (msg.t === "setRepoResults") {
setRepoResults((oldRepoResults) => {
const newRepoIds = msg.repoResults.map((r) => r.repositoryId);
return [
...oldRepoResults.filter((v) => !newRepoIds.includes(v.repositoryId)),
...msg.repoResults,
];
});
} else if (msg.t === "setRepoStates") {
setRepoStates((oldRepoStates) => {
const newRepoIds = msg.repoStates.map((r) => r.repositoryId);
return [
...oldRepoStates.filter((v) => !newRepoIds.includes(v.repositoryId)),
...msg.repoStates,
];
});
}
}, []);
const copyRepositoryList = useCallback(() => {