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
6 import { createEffect, createMemo, createState } from 'solid-js';
7 import { For, Show } from 'solid-js/dom';
8 import { TaxReturn, Form, Line, FormatType } from 'ustaxlib/core';
9 import * as Trace from 'ustaxlib/core/Trace';
10 import { Edge } from 'ustaxlib/core/Trace';
11 import { graphviz } from 'd3-graphviz';
13 const S = require('./FormView.css');
20 export default function FormView(props: FormProps) {
21 const lines = createMemo(() => {
22 const keys = Object.keys(props.form.lines);
23 keys.sort((a, b) => a.localeCompare(b, undefined, { numeric: true }));
24 return keys.map(k => props.form.lines[k]);
29 <h2 class={S.formName}>Form {props.form.name}</h2>
33 {line => <LineView tr={props.tr} line={line} />}
46 private static _instance: Formatter;
48 private _dollar = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', 'currencySign': 'accounting' } as Intl.NumberFormatOptions);
49 private _percent = new Intl.NumberFormat('en-US', { style: 'percent', maximumFractionDigits: 3 });
50 private _decimal = new Intl.NumberFormat('en-US', { style: 'decimal', minimumFractionDigits: 5 });
52 private constructor() {}
54 static getInstance() {
55 if (!Formatter._instance) {
56 Formatter._instance = new Formatter();
58 return Formatter._instance;
61 dollar(value: number): string {
62 return this._dollar.format(value);
65 percent(value: number): string {
66 return this._percent.format(value);
69 decimal(value: number): string {
70 return this._decimal.format(value);
73 string(value: any): string {
74 return JSON.stringify(value, null, 1);
78 function formatLine(value: any, line: Line<any>): string {
79 const formatter = Formatter.getInstance();
80 const formatType = line.options.formatType;
82 if (typeof(value) === 'number') {
83 if (formatType == FormatType.Decimal) {
84 return formatter.decimal(value);
85 } else if (formatType == FormatType.Percent) {
86 return formatter.percent(value);
87 } else if (formatType == FormatType.Dollar || formatType == undefined) {
88 return formatter.dollar(value);
92 return formatter.string(value);
95 function LineView(props: LineProps) {
96 const { tr, line } = props;
98 const [ state, setState ] = createState({
99 value: undefined as any,
100 error: undefined as any,
101 trace: [] as readonly Edge[],
109 trace: [] as readonly Edge[]
113 newState.value = line.value(tr);
117 newState.trace = Trace.getLastTraceList();
121 const valueDisplay = createMemo(() => {
123 return <span class={S.error} title={state.error.stack}>{state.error.message}</span>;
125 return formatLine(state.value, line);
128 const toggleTrace = () => setState('showTrace', !state.showTrace);
133 <div class={S.id} onclick={toggleTrace}>{line.id}</div>
134 <div class={S.description}>
138 <div class={S.value}>{valueDisplay()}</div>
140 <Show when={state.showTrace}>
141 <TraceViewer line={line} trace={state.trace} onClose={() => setState('showTrace', false)} />
147 interface TraceProps {
149 trace: readonly Edge[];
154 function TraceViewer(props: TraceProps) {
155 const renderGraph = (ref) => {
157 for (const edge of props.trace) {
158 graph += `"${edge[1]}" -> "${edge[0]}"; `;
161 .zoomScaleExtent([0.1, 1])
162 .renderDot(`digraph { ${graph} }`, () => {
163 if (ref.querySelector('svg').clientWidth > ref.parentNode.clientWidth) {
164 ref.parentNode.classList.add(S.large);
169 <div class={S.traceViewer}>
170 <h2>Trace {props.line.id} <button class={S.close} onclick={props.onClose}>\u24E7</button></h2>
171 <div forwardRef={renderGraph}></div>