Make the trace viewer easier to use for large graphs.
[ustaxviewer.git] / src / FormView.tsx
1 // Copyright 2020 Blue Static <https://www.bluestatic.org>
2 // This program is free software licensed under the GNU General Public License,
3 // version 3.0. The full text of the license can be found in LICENSE.txt.
4 // SPDX-License-Identifier: GPL-3.0-only
5
6 import { createDependentEffect, createMemo, createState } from 'solid-js';
7 import { For, Show } from 'solid-js/dom';
8 import { TaxReturn, Form, Line } from 'ustaxlib/core';
9 import { Edge, getLastTraceList } from 'ustaxlib/core/Trace';
10 import { graphviz } from 'd3-graphviz';
11
12 const S = require('./FormView.css');
13
14 interface FormProps {
15 tr: TaxReturn;
16 form: Form<any>;
17 }
18
19 export default function FormView(props: FormProps) {
20 const lines = createMemo(() => {
21 const keys = Object.keys(props.form.lines);
22 keys.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
23 return keys.map(k => props.form.lines[k]);
24 });
25
26 return (
27 <div class={S.form}>
28 <h2 class={S.formName}>Form {props.form.name}</h2>
29
30 <div class={S.table}>
31 <For each={lines()}>
32 {line => <LineView tr={props.tr} line={line} />}
33 </For>
34 </div>
35 </div>
36 );
37 }
38
39 interface LineProps {
40 tr: TaxReturn;
41 line: Line<any>;
42 }
43
44 function LineView(props: LineProps) {
45 const { tr, line } = props;
46 const value = createMemo(() => {
47 try {
48 return JSON.stringify(line.value(tr), null, 1);
49 } catch (e) {
50 return <span class={S.error} title={e.stack}>{e.message}</span>;
51 }
52 });
53
54 const [ state, setState ] = createState({
55 trace: [] as readonly Edge[],
56 showTrace: false
57 });
58
59 createDependentEffect(() => setState('trace', getLastTraceList()), [value]);
60
61 const toggleTrace = () => setState('showTrace', !state.showTrace);
62
63 return (
64 <>
65 <div class={S.line}>
66 <div class={S.id} onclick={toggleTrace}>{line.id}</div>
67 <div class={S.description}>
68 {line.description}
69
70 </div>
71 <div class={S.value}>{value()}</div>
72 </div>
73 <Show when={state.showTrace}>
74 <TraceViewer line={line} trace={state.trace} onClose={() => setState('showTrace', false)} />
75 </Show>
76 </>
77 );
78 }
79
80 interface TraceProps {
81 line: Line<any>;
82 trace: readonly Edge[];
83
84 onClose: () => void;
85 }
86
87 function TraceViewer(props: TraceProps) {
88 const renderGraph = (ref) => {
89 let graph = '';
90 for (const edge of props.trace) {
91 graph += `"${edge[1]}" -> "${edge[0]}"; `;
92 }
93 graphviz(ref)
94 .zoomScaleExtent([0.1, 1])
95 .renderDot(`digraph { ${graph} }`, () => {
96 if (ref.querySelector('svg').clientWidth > ref.parentNode.clientWidth) {
97 ref.parentNode.classList.add(S.large);
98 }
99 });
100 };
101 return (
102 <div class={S.traceViewer}>
103 <h2>Trace {props.line.id} <button class={S.close} onclick={props.onClose}>\u24E7</button></h2>
104 <div forwardRef={renderGraph}></div>
105 </div>
106 );
107 }