@kwirthmagnify/kwirth-plugin-provider-debug 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/front.js +128 -9
  2. package/package.json +1 -1
package/front.js CHANGED
@@ -232,6 +232,27 @@
232
232
  if (token === "true" || token === "false" || token === "null") return theme.palette.secondary.main;
233
233
  return theme.palette.warning.main;
234
234
  };
235
+ const paint = (text, color, keyBase) => {
236
+ const needle = (props.highlight ?? "").trim();
237
+ if (needle === "") return [/* @__PURE__ */ import_react2.default.createElement("span", { key: keyBase, style: color ? { color } : void 0 }, text)];
238
+ const nodes = [];
239
+ const lower = text.toLowerCase();
240
+ const target = needle.toLowerCase();
241
+ let from = 0;
242
+ let at = lower.indexOf(target);
243
+ while (at >= 0) {
244
+ if (at > from) nodes.push(/* @__PURE__ */ import_react2.default.createElement("span", { key: `${keyBase}-${from}`, style: color ? { color } : void 0 }, text.slice(from, at)));
245
+ nodes.push(
246
+ // el marcador permite que el buscador centre la COINCIDENCIA y no la tarjeta: con un
247
+ // JSON de miles de líneas, centrar la tarjeta deja el resultado fuera de pantalla
248
+ /* @__PURE__ */ import_react2.default.createElement("span", { key: `${keyBase}-h${at}`, "data-pd-hit": "1", style: { backgroundColor: theme.palette.text.primary, color: theme.palette.background.paper } }, text.slice(at, at + needle.length))
249
+ );
250
+ from = at + needle.length;
251
+ at = lower.indexOf(target, from);
252
+ }
253
+ if (from < text.length) nodes.push(/* @__PURE__ */ import_react2.default.createElement("span", { key: `${keyBase}-${from}`, style: color ? { color } : void 0 }, text.slice(from)));
254
+ return nodes;
255
+ };
235
256
  const render = () => {
236
257
  let text;
237
258
  try {
@@ -244,11 +265,11 @@
244
265
  let match;
245
266
  TOKEN.lastIndex = 0;
246
267
  while ((match = TOKEN.exec(text)) !== null) {
247
- if (match.index > last) nodes.push(/* @__PURE__ */ import_react2.default.createElement("span", { key: `p${last}` }, text.slice(last, match.index)));
248
- nodes.push(/* @__PURE__ */ import_react2.default.createElement("span", { key: `t${match.index}`, style: { color: colorOf(match[0]) } }, match[0]));
268
+ if (match.index > last) nodes.push(...paint(text.slice(last, match.index), void 0, `p${last}`));
269
+ nodes.push(...paint(match[0], colorOf(match[0]), `t${match.index}`));
249
270
  last = match.index + match[0].length;
250
271
  }
251
- if (last < text.length) nodes.push(/* @__PURE__ */ import_react2.default.createElement("span", { key: `p${last}` }, text.slice(last)));
272
+ if (last < text.length) nodes.push(...paint(text.slice(last), void 0, `p${last}`));
252
273
  return nodes;
253
274
  };
254
275
  return /* @__PURE__ */ import_react2.default.createElement(import_material2.Box, { component: "pre", sx: { m: 0, fontFamily: "monospace", fontSize: "0.75rem", whiteSpace: "pre-wrap", wordBreak: "break-all", overflowX: "auto" } }, render());
@@ -263,14 +284,56 @@
263
284
  const [boxTop, setBoxTop] = (0, import_react3.useState)(0);
264
285
  const [, forceRender] = (0, import_react3.useState)(0);
265
286
  const [copied, setCopied] = (0, import_react3.useState)(null);
287
+ const [search, setSearch] = (0, import_react3.useState)("");
288
+ const [matchPos, setMatchPos] = (0, import_react3.useState)(-1);
289
+ const [expanded, setExpanded] = (0, import_react3.useState)(/* @__PURE__ */ new Set());
290
+ const cardRefs = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
291
+ const searchText = (0, import_react3.useRef)(/* @__PURE__ */ new WeakMap());
266
292
  (0, import_react3.useEffect)(() => {
267
293
  if (boxRef.current) setBoxTop(boxRef.current.getBoundingClientRect().top);
268
294
  });
269
295
  const clear = () => {
270
296
  data.events = [];
271
297
  data.signals = [];
298
+ setExpanded(/* @__PURE__ */ new Set());
299
+ cardRefs.current.clear();
300
+ setMatchPos(-1);
272
301
  forceRender((n) => n + 1);
273
302
  };
303
+ const textOf = (event) => {
304
+ const cached = searchText.current.get(event);
305
+ if (cached !== void 0) return cached;
306
+ let text;
307
+ try {
308
+ text = (event.providerId + " " + JSON.stringify(event.event)).toLowerCase();
309
+ } catch {
310
+ text = event.providerId.toLowerCase();
311
+ }
312
+ searchText.current.set(event, text);
313
+ return text;
314
+ };
315
+ const matches = () => {
316
+ const needle = search.trim().toLowerCase();
317
+ if (needle === "") return [];
318
+ return data.events.filter((event) => textOf(event).includes(needle));
319
+ };
320
+ const isMatch = (event) => {
321
+ const needle = search.trim().toLowerCase();
322
+ return needle !== "" && textOf(event).includes(needle);
323
+ };
324
+ const goToMatch = (pos) => {
325
+ const found2 = matches();
326
+ if (found2.length === 0) return;
327
+ const next = (pos % found2.length + found2.length) % found2.length;
328
+ const target = found2[next];
329
+ setMatchPos(next);
330
+ setExpanded((prev) => new Set(prev).add(target));
331
+ setTimeout(() => {
332
+ const card = cardRefs.current.get(target);
333
+ const hit = card?.querySelector("[data-pd-hit]");
334
+ (hit ?? card)?.scrollIntoView({ block: "center" });
335
+ }, 0);
336
+ };
274
337
  const writeClipboard = async (text) => {
275
338
  try {
276
339
  await navigator.clipboard.writeText(text);
@@ -295,7 +358,7 @@
295
358
  writeClipboard(JSON.stringify(event.event, null, 2)).then((ok) => {
296
359
  if (!ok) return;
297
360
  setCopied(event);
298
- setTimeout(() => setCopied((current) => current === event ? null : current), 1500);
361
+ setTimeout(() => setCopied((current2) => current2 === event ? null : current2), 1500);
299
362
  });
300
363
  };
301
364
  const summaryOf = (event) => {
@@ -311,14 +374,70 @@
311
374
  if (data.providers.length === 0) return /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "body2", color: "text.secondary" }, "No providers running.");
312
375
  return /* @__PURE__ */ import_react3.default.createElement(import_material3.Stack, { direction: "row", spacing: 1, flexWrap: "wrap", useFlexGap: true }, data.providers.map((p) => /* @__PURE__ */ import_react3.default.createElement(import_material3.Chip, { key: p.id, label: p.id, size: "small", variant: p.id === instanceConfig.providerId ? "filled" : "outlined" })));
313
376
  };
314
- const formatEvent = (event, index) => /* @__PURE__ */ import_react3.default.createElement(import_material3.Accordion, { key: index, disableGutters: true, slotProps: { transition: { unmountOnExit: true } } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.AccordionSummary, { expandIcon: /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.ExpandMore, null) }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Stack, { direction: "row", spacing: 1.5, alignItems: "center", sx: { minWidth: 0, width: "100%" } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary", sx: { fontFamily: "monospace" } }, new Date(event.ts).toISOString()), /* @__PURE__ */ import_react3.default.createElement(import_material3.Chip, { label: event.providerId, size: "small", variant: "outlined", sx: { fontSize: "0.65rem", height: 18 } }), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary", noWrap: true }, summaryOf(event.event)), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tooltip, { title: copied === event ? "Copied" : "Copy event JSON" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.IconButton, { size: "small", "aria-label": "Copy event JSON", sx: { ml: "auto" }, onClick: (e) => {
315
- e.stopPropagation();
316
- copy(event);
317
- } }, copied === event ? /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.Check, { fontSize: "small", color: "success" }) : /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.ContentCopy, { fontSize: "small" }))))), /* @__PURE__ */ import_react3.default.createElement(import_material3.AccordionDetails, null, /* @__PURE__ */ import_react3.default.createElement(JsonBlock, { value: event.event })));
377
+ const toggle = (event) => setExpanded((prev) => {
378
+ const next = new Set(prev);
379
+ if (next.has(event)) next.delete(event);
380
+ else next.add(event);
381
+ return next;
382
+ });
383
+ const formatEvent = (event, index, current2) => {
384
+ const open = expanded.has(event);
385
+ return /* @__PURE__ */ import_react3.default.createElement(
386
+ import_material3.Paper,
387
+ {
388
+ key: index,
389
+ variant: "outlined",
390
+ ref: (el) => {
391
+ if (el) cardRefs.current.set(event, el);
392
+ else cardRefs.current.delete(event);
393
+ },
394
+ sx: { mb: 0.5, ...current2 === event ? { outline: 2, outlineColor: "warning.main", outlineOffset: -2 } : {} }
395
+ },
396
+ /* @__PURE__ */ import_react3.default.createElement(import_material3.Stack, { direction: "row", spacing: 1.5, alignItems: "center", sx: { minWidth: 0, px: 1, py: 0.5, cursor: "pointer" }, onClick: () => toggle(event) }, /* @__PURE__ */ import_react3.default.createElement(import_material3.IconButton, { size: "small", "aria-label": open ? "Collapse event" : "Expand event", onClick: (e) => {
397
+ e.stopPropagation();
398
+ toggle(event);
399
+ } }, open ? /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.ExpandLess, { fontSize: "small" }) : /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.ExpandMore, { fontSize: "small" })), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: isMatch(event) ? "warning.main" : "text.secondary", sx: { fontFamily: "monospace" } }, new Date(event.ts).toISOString()), /* @__PURE__ */ import_react3.default.createElement(import_material3.Chip, { label: event.providerId, size: "small", variant: "outlined", sx: { fontSize: "0.65rem", height: 18 } }), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary", noWrap: true }, summaryOf(event.event)), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tooltip, { title: copied === event ? "Copied" : "Copy event JSON" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.IconButton, { size: "small", "aria-label": "Copy event JSON", sx: { ml: "auto" }, onClick: (e) => {
400
+ e.stopPropagation();
401
+ copy(event);
402
+ } }, copied === event ? /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.Check, { fontSize: "small", color: "success" }) : /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.ContentCopy, { fontSize: "small" })))),
403
+ open && /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { sx: { px: 2, pb: 1 } }, /* @__PURE__ */ import_react3.default.createElement(JsonBlock, { value: event.event, highlight: search }))
404
+ );
405
+ };
318
406
  if (!data.started) {
319
407
  return /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { sx: { p: 2 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { color: "text.secondary" }, "Provider Debug not started. Start the channel (tab settings \u2699 \u2192 Start) to subscribe to a provider and watch its raw events."));
320
408
  }
321
- return /* @__PURE__ */ import_react3.default.createElement(import_material3.Card, { sx: { flex: 1, width: "98%", alignSelf: "center", m: 1 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.CardHeader, { title: /* @__PURE__ */ import_react3.default.createElement(import_material3.Stack, { direction: "row", alignItems: "center", sx: { width: "100%" } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { mr: 4 }, /* @__PURE__ */ import_react3.default.createElement("b", null, "Provider:"), " ", instanceConfig.providerId || "(none)"), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { mr: 4 }, /* @__PURE__ */ import_react3.default.createElement("b", null, "Events:"), " ", data.events.length, " / ", config.maxEvents), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { mr: 4 }, /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.Info, { fontSize: "small", sx: { mb: 0.25 } }), /* @__PURE__ */ import_react3.default.createElement("b", null, "\xA0Status:"), " ", data.paused ? "paused" : data.started ? "started" : "stopped"), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tooltip, { title: "Clear captured events" }, /* @__PURE__ */ import_react3.default.createElement("span", { style: { marginLeft: "auto" } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.IconButton, { size: "small", "aria-label": "Clear captured events", onClick: clear, disabled: data.events.length === 0 && data.signals.length === 0 }, /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.DeleteSweep, { fontSize: "small" }))))) }), /* @__PURE__ */ import_react3.default.createElement(import_material3.CardContent, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Stack, { direction: "column", spacing: 1, sx: { mb: 1 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary" }, "Running providers"), formatProviders(), data.signals.map((s, index) => /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { key: index, variant: "caption", color: "text.secondary" }, "*** ", s, " ***"))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { ref: boxRef, sx: { display: "flex", flexDirection: "column", overflowY: "auto", overflowX: "hidden", width: "100%", flexGrow: 1, height: `calc(100vh - ${boxTop}px - 35px)` } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { sx: { flex: 1, overflowY: "auto", ml: 1, mr: 1 } }, data.events.map((e, index) => formatEvent(e, index))))));
409
+ const found = matches();
410
+ const current = matchPos >= 0 && matchPos < found.length ? found[matchPos] : void 0;
411
+ return /* @__PURE__ */ import_react3.default.createElement(import_material3.Card, { sx: { flex: 1, width: "98%", alignSelf: "center", m: 1 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.CardHeader, { title: /* @__PURE__ */ import_react3.default.createElement(import_material3.Stack, { direction: "row", alignItems: "center", sx: { width: "100%" } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { mr: 4 }, /* @__PURE__ */ import_react3.default.createElement("b", null, "Provider:"), " ", instanceConfig.providerId || "(none)"), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { mr: 4 }, /* @__PURE__ */ import_react3.default.createElement("b", null, "Events:"), " ", data.events.length, " / ", config.maxEvents), /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { mr: 4 }, /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.Info, { fontSize: "small", sx: { mb: 0.25 } }), /* @__PURE__ */ import_react3.default.createElement("b", null, "\xA0Status:"), " ", data.paused ? "paused" : data.started ? "started" : "stopped"), /* @__PURE__ */ import_react3.default.createElement(import_material3.Stack, { direction: "row", alignItems: "center", spacing: 0.5, sx: { ml: "auto" } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: found.length === 0 && search.trim() !== "" ? "warning.main" : "text.secondary", sx: { minWidth: 44, textAlign: "right" } }, `${matchPos + 1}/${found.length}`), /* @__PURE__ */ import_react3.default.createElement(
412
+ import_material3.TextField,
413
+ {
414
+ value: search,
415
+ onChange: (e) => {
416
+ setSearch(e.target.value);
417
+ setMatchPos(-1);
418
+ },
419
+ onKeyDown: (e) => {
420
+ if (e.key !== "Enter") return;
421
+ e.preventDefault();
422
+ goToMatch(e.shiftKey ? matchPos - 1 : matchPos + 1);
423
+ },
424
+ placeholder: "Search events",
425
+ variant: "standard",
426
+ sx: { width: 200 },
427
+ slotProps: {
428
+ htmlInput: { "aria-label": "Search events" },
429
+ input: {
430
+ startAdornment: /* @__PURE__ */ import_react3.default.createElement(import_material3.InputAdornment, { position: "start" }, /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.Search, { fontSize: "small" })),
431
+ // siempre presente y deshabilitado: si se renderiza en
432
+ // condicional desaparece bajo el propio click que lo pulsa
433
+ endAdornment: /* @__PURE__ */ import_react3.default.createElement(import_material3.InputAdornment, { position: "end" }, /* @__PURE__ */ import_react3.default.createElement(import_material3.IconButton, { size: "small", "aria-label": "Clear search", disabled: search === "", onClick: () => {
434
+ setSearch("");
435
+ setMatchPos(-1);
436
+ } }, /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.Clear, { fontSize: "small" })))
437
+ }
438
+ }
439
+ }
440
+ ), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tooltip, { title: "Previous match (Shift+Enter)" }, /* @__PURE__ */ import_react3.default.createElement("span", null, /* @__PURE__ */ import_react3.default.createElement(import_material3.IconButton, { size: "small", "aria-label": "Previous match", disabled: found.length === 0, onClick: () => goToMatch(matchPos - 1) }, /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.ArrowUpward, { fontSize: "small" })))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tooltip, { title: "Next match (Enter)" }, /* @__PURE__ */ import_react3.default.createElement("span", null, /* @__PURE__ */ import_react3.default.createElement(import_material3.IconButton, { size: "small", "aria-label": "Next match", disabled: found.length === 0, onClick: () => goToMatch(matchPos + 1) }, /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.ArrowDownward, { fontSize: "small" })))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Tooltip, { title: "Clear captured events" }, /* @__PURE__ */ import_react3.default.createElement("span", null, /* @__PURE__ */ import_react3.default.createElement(import_material3.IconButton, { size: "small", "aria-label": "Clear captured events", onClick: clear, disabled: data.events.length === 0 && data.signals.length === 0 }, /* @__PURE__ */ import_react3.default.createElement(import_icons_material2.DeleteSweep, { fontSize: "small" })))))) }), /* @__PURE__ */ import_react3.default.createElement(import_material3.CardContent, null, /* @__PURE__ */ import_react3.default.createElement(import_material3.Stack, { direction: "column", spacing: 1, sx: { mb: 1 } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Stack, { direction: "row", spacing: 1, alignItems: "center", flexWrap: "wrap", useFlexGap: true }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { variant: "caption", color: "text.secondary" }, "Running providers"), formatProviders()), data.signals.map((s, index) => /* @__PURE__ */ import_react3.default.createElement(import_material3.Typography, { key: index, variant: "caption", color: "text.secondary" }, "*** ", s, " ***"))), /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { ref: boxRef, sx: { display: "flex", flexDirection: "column", overflowY: "auto", overflowX: "hidden", width: "100%", flexGrow: 1, height: `calc(100vh - ${boxTop}px - 35px)` } }, /* @__PURE__ */ import_react3.default.createElement(import_material3.Box, { sx: { flex: 1, overflowY: "auto", ml: 1, mr: 1 } }, data.events.map((e, index) => formatEvent(e, index, current))))));
322
441
  };
323
442
 
324
443
  // src/front/ProviderDebugChannel.ts
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "id": "provider-debug",
5
5
  "name": "@kwirthmagnify/kwirth-plugin-provider-debug",
6
6
  "displayName": "Provider Debug",
7
- "version": "0.1.0",
7
+ "version": "0.1.1",
8
8
  "description": "Provider debugging channel for Kwirth - subscribes to a running provider and streams its raw events",
9
9
  "icon": "DataObjectOutlined",
10
10
  "requiresRestart": false,