@kanaries/graphic-walker 0.4.0 → 0.4.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.
@@ -1 +1,2 @@
1
1
  export declare function useDebounceValue<T>(value: T, timeout?: number): T;
2
+ export declare function useDebounceValueBind<T>(value: T, setter: (v: T) => void, timeout?: number): [T, (v: T) => void];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kanaries/graphic-walker",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "scripts": {
5
5
  "dev:front_end": "vite --host",
6
6
  "dev": "npm run dev:front_end",
@@ -5,6 +5,7 @@ import { PaperAirplaneIcon } from '@heroicons/react/24/outline';
5
5
  import Spinner from '../spinner';
6
6
  import { IViewField } from '../../interfaces';
7
7
  import { VisSpecWithHistory } from '../../models/visSpecHistory';
8
+ import { visSpecDecoder, forwardVisualConfigs } from '../../utils/save';
8
9
 
9
10
  type VEGALite = any;
10
11
 
@@ -51,7 +52,7 @@ const AskViz: React.FC<{api?: string; headers?: Record<string, string>}> = (prop
51
52
  setLoading(true);
52
53
  vizQuery(props.api ?? api, allFields, query, props.headers ?? {})
53
54
  .then((data) => {
54
- vizStore.visList.push(new VisSpecWithHistory(data));
55
+ vizStore.visList.push(new VisSpecWithHistory(visSpecDecoder(forwardVisualConfigs([data]))[0]));
55
56
  vizStore.selectVisualization(vizStore.visList.length - 1);
56
57
  // const liteGW = parseGW(spec);
57
58
  // vizStore.renderSpec(liteGW);
@@ -1,7 +1,7 @@
1
1
  import React, { useMemo, useState, useRef, useEffect } from 'react';
2
2
  import styled from 'styled-components';
3
3
  import { observer } from 'mobx-react-lite';
4
- import type { IMutField, IRow, DataSet } from '../../interfaces';
4
+ import type { IMutField, IRow, DataSet, IComputationFunction } from '../../interfaces';
5
5
  import { useTranslation } from 'react-i18next';
6
6
  import LoadingLayer from "../loadingLayer";
7
7
  import { useComputationFunc } from "../../renderer/hooks";
@@ -16,6 +16,7 @@ interface DataTableProps {
16
16
  /** total count of rows */
17
17
  total: number;
18
18
  dataset: DataSet;
19
+ computation?: IComputationFunction;
19
20
  onMetaChange: (fid: string, fIndex: number, meta: Partial<IMutField>) => void;
20
21
  loading?: boolean;
21
22
  }
@@ -117,10 +118,11 @@ const getHeaderKey = (f: wrapMutField) => {
117
118
  };
118
119
 
119
120
  const DataTable: React.FC<DataTableProps> = (props) => {
120
- const { size = 10, onMetaChange, dataset, total, loading: statLoading } = props;
121
+ const { size = 10, onMetaChange, dataset, computation, total, loading: statLoading } = props;
121
122
  const [pageIndex, setPageIndex] = useState(0);
122
123
  const { t } = useTranslation();
123
- const computationFuction = useComputationFunc();
124
+ const defaultComputation = useComputationFunc();
125
+ const computationFunction = computation ?? defaultComputation;
124
126
 
125
127
  const analyticTypeList = useMemo<{ value: string; label: string }[]>(() => {
126
128
  return ANALYTIC_TYPE_LIST.map((at) => ({
@@ -149,7 +151,7 @@ const DataTable: React.FC<DataTableProps> = (props) => {
149
151
  }
150
152
  setDataLoading(true);
151
153
  const taskId = ++taskIdRef.current;
152
- dataReadRawServer(computationFuction, size, pageIndex).then(data => {
154
+ dataReadRawServer(computationFunction, size, pageIndex).then(data => {
153
155
  if (taskId === taskIdRef.current) {
154
156
  setDataLoading(false);
155
157
  setRows(data);
@@ -164,7 +166,7 @@ const DataTable: React.FC<DataTableProps> = (props) => {
164
166
  return () => {
165
167
  taskIdRef.current++;
166
168
  };
167
- }, [computationFuction, pageIndex, size]);
169
+ }, [computationFunction, pageIndex, size]);
168
170
 
169
171
  const loading = statLoading || dataLoading;
170
172
 
@@ -1,8 +1,10 @@
1
1
  import React from 'react';
2
2
  import { useTranslation } from 'react-i18next';
3
+ import { useDebounceValueBind } from '../hooks';
3
4
 
4
5
  export default function LimitSetting(props: { value: number; setValue: (v: number) => void }) {
5
6
  const { t } = useTranslation('translation', { keyPrefix: 'main.tabpanel.settings' });
7
+ const [innerValue, setInnerValue] = useDebounceValueBind(props.value, v => props.setValue(v));
6
8
 
7
9
  return (
8
10
  <div className=" mt-2">
@@ -10,15 +12,15 @@ export default function LimitSetting(props: { value: number; setValue: (v: numbe
10
12
  className="w-full h-2 bg-blue-100 appearance-none"
11
13
  type="range"
12
14
  name="limit"
13
- value={props.value > 0 ? props.value : 0}
15
+ value={innerValue > 0 ? innerValue : 0}
14
16
  min="1"
15
17
  max="50"
16
- disabled={props.value < 0}
18
+ disabled={innerValue < 0}
17
19
  step="1"
18
20
  onChange={(e) => {
19
21
  const v = parseInt(e.target.value);
20
22
  if (!isNaN(v)) {
21
- props.setValue(v);
23
+ setInnerValue(v);
22
24
  }
23
25
  }}
24
26
  />
@@ -26,12 +28,12 @@ export default function LimitSetting(props: { value: number; setValue: (v: numbe
26
28
  <input
27
29
  type="checkbox"
28
30
  className="h-4 w-4 mr-1 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600"
29
- checked={props.value > 0}
31
+ checked={innerValue > 0}
30
32
  onChange={(e) => {
31
- props.setValue(e.target.checked ? 30 : -1);
33
+ setInnerValue(e.target.checked ? 30 : -1);
32
34
  }}
33
35
  ></input>
34
- {`${t('limit')}${props.value > 0 ? `: ${props.value}` : ''}`}
36
+ {`${t('limit')}${innerValue > 0 ? `: ${innerValue}` : ''}`}
35
37
  </output>
36
38
  </div>
37
39
  );
@@ -1,6 +1,7 @@
1
1
  import { ArrowsPointingOutIcon, XMarkIcon } from "@heroicons/react/24/outline";
2
2
  import React, { useState, useEffect } from "react";
3
3
  import { useTranslation } from "react-i18next";
4
+ import { useDebounceValueBind } from "../hooks";
4
5
 
5
6
  interface SizeSettingProps {
6
7
  onWidthChange: (val: number) => void;
@@ -12,7 +13,8 @@ interface SizeSettingProps {
12
13
  export const ResizeDialog: React.FC<SizeSettingProps> = (props) => {
13
14
  const { onWidthChange, onHeightChange, width, height, children } = props;
14
15
  const { t } = useTranslation("translation", { keyPrefix: "main.tabpanel.settings.size_setting" });
15
-
16
+ const [innerWidth, setInnerWidth] = useDebounceValueBind(width, onWidthChange);
17
+ const [innerHeight, setInnerHeight] = useDebounceValueBind(height, onHeightChange);
16
18
  return (
17
19
  <div className="text-zinc-400">
18
20
  {children}
@@ -22,16 +24,16 @@ export const ResizeDialog: React.FC<SizeSettingProps> = (props) => {
22
24
  style={{ cursor: "ew-resize" }}
23
25
  type="range"
24
26
  name="width"
25
- value={Math.sqrt(width / 1000)}
27
+ value={Math.sqrt(innerWidth / 1000)}
26
28
  min="0"
27
29
  max="1"
28
30
  step="0.01"
29
31
  onChange={(e) => {
30
- onWidthChange(Math.round(Number(e.target.value) ** 2 * 1000));
32
+ setInnerWidth(Math.round(Number(e.target.value) ** 2 * 1000));
31
33
  }}
32
34
  />
33
35
  <output className="text-sm ml-1" htmlFor="width">
34
- {`${t("width")}: ${width}`}
36
+ {`${t("width")}: ${innerWidth}`}
35
37
  </output>
36
38
  </div>
37
39
  <div className=" mt-2">
@@ -40,16 +42,16 @@ export const ResizeDialog: React.FC<SizeSettingProps> = (props) => {
40
42
  style={{ cursor: "ew-resize" }}
41
43
  type="range"
42
44
  name="height"
43
- value={Math.sqrt(height / 1000)}
45
+ value={Math.sqrt(innerHeight / 1000)}
44
46
  min="0"
45
47
  max="1"
46
48
  step="0.01"
47
49
  onChange={(e) => {
48
- onHeightChange(Math.round(Number(e.target.value) ** 2 * 1000));
50
+ setInnerHeight(Math.round(Number(e.target.value) ** 2 * 1000));
49
51
  }}
50
52
  />
51
53
  <output className="text-sm ml-1" htmlFor="height">
52
- {`${t("height")}: ${height}`}
54
+ {`${t("height")}: ${innerHeight}`}
53
55
  </output>
54
56
  </div>
55
57
  </div>
@@ -29,7 +29,7 @@ export const datasetStatsServer = async (service: IComputationFunction): Promise
29
29
  ],
30
30
  })) as [{ count: number }];
31
31
  return {
32
- rowCount: res[0].count,
32
+ rowCount: res[0]?.count ?? 0,
33
33
  };
34
34
  };
35
35
 
@@ -72,7 +72,7 @@ export const dataQueryServer = async (
72
72
  }
73
73
  const res = await service({
74
74
  workflow,
75
- limit
75
+ limit,
76
76
  });
77
77
  return res;
78
78
  };
@@ -132,7 +132,12 @@ export const fieldStatServer = async (
132
132
  },
133
133
  ],
134
134
  };
135
- const [rangeRes] = range
135
+ const [
136
+ rangeRes = {
137
+ [MIN_ID]: 0,
138
+ [MAX_ID]: 0,
139
+ },
140
+ ] = range
136
141
  ? await service(rangeQueryPayload)
137
142
  : [
138
143
  {
@@ -3,6 +3,7 @@ import { observer } from "mobx-react-lite";
3
3
  import { useGlobalStore } from "../store";
4
4
  import DataTable from "../components/dataTable";
5
5
  import { toJS } from "mobx";
6
+ import { getComputation } from "../computation/clientComputation";
6
7
 
7
8
  interface TableProps {
8
9
  size?: number;
@@ -21,11 +22,14 @@ const Table: React.FC<TableProps> = (props) => {
21
22
  rawFields: toJS(tmpDSRawFields),
22
23
  };
23
24
  }, [tmpDataSource, tmpDSRawFields]);
25
+ 9
26
+ const computation = React.useMemo(() => getComputation(tempDataset.dataSource), [tempDataset])
24
27
 
25
28
  return (
26
29
  <DataTable
27
30
  size={size}
28
31
  dataset={tempDataset}
32
+ computation={computation}
29
33
  total={tmpDataSource.length}
30
34
  onMetaChange={(fid, fIndex, diffMeta) => {
31
35
  commonStore.updateTempDatasetMetas(fid, diffMeta);
@@ -8,3 +8,11 @@ export function useDebounceValue<T>(value: T, timeout = 200): T {
8
8
  }, [value]);
9
9
  return innerValue;
10
10
  }
11
+
12
+ export function useDebounceValueBind<T>(value: T, setter: (v: T) => void, timeout = 200): [T, (v: T) => void] {
13
+ const [innerValue, setInnerValue] = useState(value);
14
+ const valueToSet = useDebounceValue(innerValue, timeout);
15
+ useEffect(() => setInnerValue(value), [value])
16
+ useEffect(() => setter(valueToSet), [valueToSet]);
17
+ return [innerValue, setInnerValue];
18
+ }
@@ -4,9 +4,7 @@ import type { DeepReadonly, IFilterField, IRow, IViewField, IDataQueryWorkflowSt
4
4
  import { useGlobalStore } from '../store';
5
5
  import { useAppRootContext } from '../components/appRoot';
6
6
  import { toWorkflow } from '../utils/workflow';
7
- import { dataQueryClient } from '../computation/clientComputation';
8
7
  import { dataQueryServer } from '../computation/serverComputation';
9
- import { useDebounceValue } from '../hooks';
10
8
 
11
9
  export const useComputationFunc = (): IComputationFunction => {
12
10
  const { vizStore } = useGlobalStore();
@@ -40,14 +38,12 @@ export const useRenderer = (props: UseRendererProps): UseRendererResult => {
40
38
  filters,
41
39
  defaultAggregated,
42
40
  sort,
43
- limit: storeLimit,
41
+ limit,
44
42
  computationFunction,
45
43
  } = props;
46
44
  const [computing, setComputing] = useState(false);
47
45
  const taskIdRef = useRef(0);
48
46
 
49
- const limit = useDebounceValue(storeLimit);
50
-
51
47
  const workflow = useMemo(() => {
52
48
  return toWorkflow(
53
49
  filters,
@@ -65,48 +61,6 @@ export const useRenderer = (props: UseRendererProps): UseRendererResult => {
65
61
 
66
62
  const appRef = useAppRootContext();
67
63
 
68
- // useEffect(() => {
69
- // if (computationMode !== 'client') {
70
- // return;
71
- // }
72
- // if (!data) {
73
- // console.warn('useRenderer error: prop `data` is required for "client" mode, but none is found.');
74
- // return;
75
- // }
76
- // if (!allFields) {
77
- // console.warn('useRenderer error: prop `fields` is required for "client" mode, but none is found.');
78
- // return;
79
- // }
80
- // const taskId = ++taskIdRef.current;
81
- // appRef.current?.updateRenderStatus('computing');
82
- // setComputing(true);
83
- // dataQueryClient(data, allFields, workflow).then(data => {
84
- // if (taskId !== taskIdRef.current) {
85
- // return;
86
- // }
87
- // appRef.current?.updateRenderStatus('rendering');
88
- // unstable_batchedUpdates(() => {
89
- // setComputing(false);
90
- // setViewData(data);
91
- // setParsedWorkflow(workflow);
92
- // });
93
- // }).catch((err) => {
94
- // if (taskId !== taskIdRef.current) {
95
- // return;
96
- // }
97
- // appRef.current?.updateRenderStatus('error');
98
- // console.error(err);
99
- // unstable_batchedUpdates(() => {
100
- // setComputing(false);
101
- // setViewData([]);
102
- // setParsedWorkflow([]);
103
- // });
104
- // });
105
- // return () => {
106
- // taskIdRef.current++;
107
- // };
108
- // }, [computationMode, data, allFields, workflow]);
109
-
110
64
  useEffect(() => {
111
65
  const taskId = ++taskIdRef.current;
112
66
  appRef.current?.updateRenderStatus('computing');
@@ -1,4 +1,4 @@
1
- import { IReactionDisposer, makeAutoObservable, observable, reaction, toJS } from 'mobx';
1
+ import { IReactionDisposer, makeAutoObservable, observable, computed, reaction, toJS } from 'mobx';
2
2
  import produce from 'immer';
3
3
  import {
4
4
  DataSet,
@@ -395,6 +395,7 @@ export class VizSpecStore {
395
395
  case configKey === 'sorted':
396
396
  case configKey === 'zeroScale':
397
397
  case configKey === 'background':
398
+ case configKey === 'resolve':
398
399
  case configKey === 'limit':
399
400
  case configKey === 'stack': {
400
401
  return (config[configKey] = value);
@@ -37,7 +37,6 @@ import throttle from '../utils/throttle';
37
37
  import KanariesLogo from '../assets/kanaries.png';
38
38
  import { ImageWithFallback } from '../components/timeoutImg';
39
39
  import LimitSetting from '../components/limitSetting';
40
- import { runInAction } from 'mobx';
41
40
 
42
41
  const Invisible = styled.div`
43
42
  clip: rect(1px, 1px, 1px, 1px);