mirror of
https://github.com/ecomfe/vue-echarts.git
synced 2025-10-27 10:55:07 +08:00
157 lines
3.6 KiB
TypeScript
157 lines
3.6 KiB
TypeScript
import { onBeforeUnmount, reactive, ref, watch } from "vue";
|
|
import type { Ref } from "vue";
|
|
import type { MonacoMarkerLike, MonacoSeverity } from "../services/monaco";
|
|
import OptionWorker from "../workers/option.worker?worker";
|
|
|
|
export interface AnalyzerDiagnostic extends MonacoMarkerLike {
|
|
severity: MonacoSeverity;
|
|
}
|
|
|
|
export interface AnalyzerIssueRange {
|
|
startLineNumber: number;
|
|
startColumn: number;
|
|
endLineNumber: number;
|
|
endColumn: number;
|
|
}
|
|
|
|
export type AnalyzerIssueKind = "syntax" | "runtime" | "format";
|
|
|
|
export interface AnalyzerIssue {
|
|
kind: AnalyzerIssueKind;
|
|
severity: MonacoSeverity;
|
|
message: string;
|
|
hint?: string;
|
|
range?: AnalyzerIssueRange;
|
|
}
|
|
|
|
interface WorkerMessage {
|
|
id: number;
|
|
strategy: "expression" | "module";
|
|
diagnostics: AnalyzerDiagnostic[];
|
|
issues: AnalyzerIssue[];
|
|
output?: string;
|
|
option?: unknown;
|
|
runtimeError?: string;
|
|
}
|
|
|
|
interface WorkerRequest {
|
|
id: number;
|
|
code: string;
|
|
}
|
|
|
|
type AnalyzerStatus = "idle" | "analyzing" | "ready" | "error";
|
|
|
|
export interface OptionAnalysisState {
|
|
status: AnalyzerStatus;
|
|
strategy: "expression" | "module";
|
|
diagnostics: AnalyzerDiagnostic[];
|
|
issues: AnalyzerIssue[];
|
|
runtimeError: string | null;
|
|
option: unknown;
|
|
output: string | null;
|
|
hasBlockingIssue: boolean;
|
|
}
|
|
|
|
export interface UseOptionAnalysisResult {
|
|
code: Ref<string>;
|
|
state: OptionAnalysisState;
|
|
updateSource(code: string): void;
|
|
dispose(): void;
|
|
}
|
|
|
|
const ANALYZE_DELAY = 120;
|
|
|
|
export function useOptionAnalysis(
|
|
initialCode: string,
|
|
): UseOptionAnalysisResult {
|
|
const isBrowser = typeof window !== "undefined";
|
|
const worker = isBrowser ? new OptionWorker() : null;
|
|
const code = ref(initialCode);
|
|
const state = reactive<OptionAnalysisState>({
|
|
status: "idle",
|
|
strategy: "expression",
|
|
diagnostics: [],
|
|
issues: [],
|
|
runtimeError: null,
|
|
option: null,
|
|
output: null,
|
|
hasBlockingIssue: false,
|
|
});
|
|
|
|
let requestId = 0;
|
|
let latestRequest = 0;
|
|
let timer: number | null = null;
|
|
|
|
const postWork = (source: string) => {
|
|
if (!worker || !isBrowser) {
|
|
return;
|
|
}
|
|
if (timer !== null) {
|
|
window.clearTimeout(timer);
|
|
}
|
|
timer = window.setTimeout(() => {
|
|
state.status = "analyzing";
|
|
state.issues = [];
|
|
state.hasBlockingIssue = false;
|
|
state.runtimeError = null;
|
|
requestId += 1;
|
|
latestRequest = requestId;
|
|
const payload: WorkerRequest = { id: requestId, code: source };
|
|
worker.postMessage(payload);
|
|
}, ANALYZE_DELAY);
|
|
};
|
|
|
|
const handleMessage = (event: MessageEvent<WorkerMessage>) => {
|
|
const { id, diagnostics, issues, option, output, runtimeError, strategy } =
|
|
event.data;
|
|
if (id !== latestRequest) {
|
|
return;
|
|
}
|
|
|
|
state.strategy = strategy;
|
|
state.diagnostics = diagnostics;
|
|
state.issues = issues;
|
|
state.hasBlockingIssue = issues.some((item) => item.severity === "error");
|
|
state.output = output ?? null;
|
|
state.option = state.hasBlockingIssue ? null : (option ?? null);
|
|
state.runtimeError = runtimeError ?? null;
|
|
state.status = state.hasBlockingIssue ? "error" : "ready";
|
|
};
|
|
|
|
if (worker) {
|
|
worker.addEventListener("message", handleMessage);
|
|
}
|
|
|
|
const stop = () => {
|
|
if (timer !== null) {
|
|
window.clearTimeout(timer);
|
|
timer = null;
|
|
}
|
|
if (worker) {
|
|
worker.removeEventListener("message", handleMessage);
|
|
worker.terminate();
|
|
}
|
|
};
|
|
|
|
onBeforeUnmount(stop);
|
|
|
|
if (worker && isBrowser) {
|
|
watch(
|
|
code,
|
|
(value) => {
|
|
postWork(value);
|
|
},
|
|
{ immediate: true },
|
|
);
|
|
}
|
|
|
|
return {
|
|
code,
|
|
state,
|
|
updateSource(next) {
|
|
code.value = next;
|
|
},
|
|
dispose: stop,
|
|
};
|
|
}
|