@kanaries/graphic-walker 0.3.16 → 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.
- package/dist/App.d.ts +9 -2
- package/dist/assets/filter.worker-f09fcd6f.js.map +1 -1
- package/dist/assets/sort.worker-f77540ac.js.map +1 -0
- package/dist/assets/transform.worker-bae8e910.js.map +1 -0
- package/dist/assets/{viewQuery.worker-03404216.js.map → viewQuery.worker-bdb6477c.js.map} +1 -1
- package/dist/components/askViz/index.d.ts +6 -0
- package/dist/components/askViz/schemaTransform.d.ts +2 -0
- package/dist/components/dataTable/index.d.ts +9 -5
- package/dist/components/pivotTable/store.d.ts +0 -2
- package/dist/components/spinner.d.ts +2 -0
- package/dist/computation/clientComputation.d.ts +3 -0
- package/dist/computation/serverComputation.d.ts +8 -0
- package/dist/config.d.ts +3 -1
- package/dist/fields/filterField/tabs.d.ts +2 -1
- package/dist/graphic-walker.es.js +22268 -21682
- package/dist/graphic-walker.es.js.map +1 -1
- package/dist/graphic-walker.umd.js +139 -139
- package/dist/graphic-walker.umd.js.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/interfaces.d.ts +93 -4
- package/dist/lib/execExp.d.ts +4 -4
- package/dist/lib/interfaces.d.ts +1 -0
- package/dist/lib/viewQuery.d.ts +2 -2
- package/dist/renderer/hooks.d.ts +8 -4
- package/dist/renderer/index.d.ts +2 -1
- package/dist/renderer/pureRenderer.d.ts +17 -1
- package/dist/renderer/specRenderer.d.ts +1 -0
- package/dist/services.d.ts +8 -5
- package/dist/store/commonStore.d.ts +2 -2
- package/dist/store/visualSpecStore.d.ts +58 -42
- package/dist/utils/save.d.ts +10 -2
- package/dist/utils/workflow.d.ts +3 -0
- package/dist/vis/react-vega.d.ts +3 -1
- package/dist/workers/sort.d.ts +2 -2
- package/dist/workers/transform.d.ts +5 -2
- package/package.json +2 -2
- package/src/App.tsx +46 -7
- package/src/components/askViz/index.tsx +93 -0
- package/src/components/askViz/schemaTransform.ts +38 -0
- package/src/components/dataTable/index.tsx +53 -11
- package/src/components/limitSetting.tsx +8 -6
- package/src/components/pivotTable/index.tsx +0 -1
- package/src/components/pivotTable/store.tsx +0 -16
- package/src/components/sizeSetting.tsx +9 -7
- package/src/components/spinner.tsx +14 -0
- package/src/components/toggle.tsx +2 -2
- package/src/components/visualConfig/index.tsx +78 -8
- package/src/computation/clientComputation.ts +55 -0
- package/src/computation/serverComputation.ts +158 -0
- package/src/config.ts +15 -2
- package/src/dataSource/datasetConfig/index.tsx +38 -6
- package/src/dataSource/table.tsx +15 -2
- package/src/fields/filterField/filterEditDialog.tsx +11 -10
- package/src/fields/filterField/tabs.tsx +178 -77
- package/src/hooks/index.ts +8 -0
- package/src/index.tsx +2 -0
- package/src/interfaces.ts +108 -5
- package/src/lib/execExp.ts +20 -11
- package/src/lib/interfaces.ts +1 -0
- package/src/lib/op/aggregate.ts +1 -1
- package/src/lib/viewQuery.ts +2 -2
- package/src/locales/en-US.json +11 -2
- package/src/locales/ja-JP.json +11 -2
- package/src/locales/zh-CN.json +11 -2
- package/src/main.tsx +1 -1
- package/src/renderer/hooks.ts +57 -69
- package/src/renderer/index.tsx +10 -6
- package/src/renderer/pureRenderer.tsx +40 -14
- package/src/renderer/specRenderer.tsx +24 -7
- package/src/services.ts +7 -8
- package/src/store/commonStore.ts +7 -7
- package/src/store/visualSpecStore.ts +288 -192
- package/src/utils/save.ts +81 -3
- package/src/utils/workflow.ts +148 -0
- package/src/vis/react-vega.tsx +21 -6
- package/src/vis/spec/aggregate.ts +3 -2
- package/src/vis/spec/stack.ts +7 -6
- package/src/visualSettings/index.tsx +2 -4
- package/src/workers/filter.worker.js +1 -1
- package/src/workers/sort.ts +3 -4
- package/src/workers/sort.worker.ts +2 -2
- package/src/workers/transform.ts +7 -8
- package/src/workers/transform.worker.js +2 -2
- package/src/workers/viewQuery.worker.js +2 -2
- package/dist/assets/sort.worker-4299a6a0.js.map +0 -1
- package/dist/assets/transform.worker-a12fb3d8.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kanaries/graphic-walker",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev:front_end": "vite --host",
|
|
6
6
|
"dev": "npm run dev:front_end",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"prettier": {
|
|
31
31
|
"tabWidth": 4,
|
|
32
|
-
"printWidth":
|
|
32
|
+
"printWidth": 160,
|
|
33
33
|
"singleQuote": true
|
|
34
34
|
},
|
|
35
35
|
"types": "./dist/index.d.ts",
|
package/src/App.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import React, { useEffect, useRef, useMemo } from 'react';
|
|
1
|
+
import React, { useEffect, useRef, useMemo, useState } from 'react';
|
|
2
2
|
import { observer } from 'mobx-react-lite';
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
|
-
import { IDarkMode, IMutField, IRow, ISegmentKey, IThemeKey, Specification } from './interfaces';
|
|
4
|
+
import { IComputationFunction, IDarkMode, IMutField, IRow, ISegmentKey, IThemeKey, Specification } from './interfaces';
|
|
5
5
|
import type { IReactVegaHandler } from './vis/react-vega';
|
|
6
6
|
import VisualSettings from './visualSettings';
|
|
7
7
|
import PosFields from './fields/posFields';
|
|
@@ -20,6 +20,8 @@ import { useCurrentMediaTheme } from './utils/media';
|
|
|
20
20
|
import CodeExport from './components/codeExport';
|
|
21
21
|
import VisualConfig from './components/visualConfig';
|
|
22
22
|
import type { ToolbarItemProps } from './components/toolbar';
|
|
23
|
+
import AskViz from './components/askViz';
|
|
24
|
+
import { getComputation } from './computation/clientComputation';
|
|
23
25
|
|
|
24
26
|
export interface IGWProps {
|
|
25
27
|
dataSource?: IRow[];
|
|
@@ -28,7 +30,7 @@ export interface IGWProps {
|
|
|
28
30
|
hideDataSourceConfig?: boolean;
|
|
29
31
|
i18nLang?: string;
|
|
30
32
|
i18nResources?: { [lang: string]: Record<string, string | any> };
|
|
31
|
-
keepAlive?: boolean;
|
|
33
|
+
keepAlive?: boolean | string;
|
|
32
34
|
/**
|
|
33
35
|
* auto parse field key into a safe string. default is true
|
|
34
36
|
*/
|
|
@@ -37,10 +39,17 @@ export interface IGWProps {
|
|
|
37
39
|
themeKey?: IThemeKey;
|
|
38
40
|
dark?: IDarkMode;
|
|
39
41
|
storeRef?: React.MutableRefObject<IGlobalStore | null>;
|
|
42
|
+
computation?: IComputationFunction;
|
|
40
43
|
toolbar?: {
|
|
41
44
|
extra?: ToolbarItemProps[];
|
|
42
45
|
exclude?: string[];
|
|
43
46
|
};
|
|
47
|
+
enhanceAPI?: {
|
|
48
|
+
header?: Record<string, string>;
|
|
49
|
+
features?: {
|
|
50
|
+
askviz?: string | boolean;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
44
53
|
}
|
|
45
54
|
|
|
46
55
|
const App = observer<IGWProps>(function App(props) {
|
|
@@ -54,7 +63,9 @@ const App = observer<IGWProps>(function App(props) {
|
|
|
54
63
|
fieldKeyGuard = true,
|
|
55
64
|
themeKey = 'vega',
|
|
56
65
|
dark = 'media',
|
|
66
|
+
computation,
|
|
57
67
|
toolbar,
|
|
68
|
+
enhanceAPI,
|
|
58
69
|
} = props;
|
|
59
70
|
const { commonStore, vizStore } = useGlobalStore();
|
|
60
71
|
|
|
@@ -75,11 +86,28 @@ const App = observer<IGWProps>(function App(props) {
|
|
|
75
86
|
}
|
|
76
87
|
}, [i18nLang, curLang]);
|
|
77
88
|
|
|
89
|
+
const [sampleRemoteData, setSampleRemoteData] = useState<IRow[]>([]);
|
|
90
|
+
const remoteDataContext = useRef(0);
|
|
91
|
+
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
if (!computation) return;
|
|
94
|
+
async () => {
|
|
95
|
+
const ts = Date.now();
|
|
96
|
+
remoteDataContext.current = ts;
|
|
97
|
+
const resp = await computation({ workflow: [{ type: 'view', query: [{ op: 'raw', fields: ['*'] }] }], limit: 1 });
|
|
98
|
+
if (remoteDataContext.current === ts) {
|
|
99
|
+
setSampleRemoteData(resp);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}, [computation]);
|
|
103
|
+
|
|
104
|
+
const remoteDataSource = dataSource.length > 0 ? dataSource : sampleRemoteData;
|
|
105
|
+
|
|
78
106
|
const safeDataset = useMemo(() => {
|
|
79
|
-
let safeData =
|
|
107
|
+
let safeData = remoteDataSource;
|
|
80
108
|
let safeMetas = rawFields;
|
|
81
109
|
if (fieldKeyGuard) {
|
|
82
|
-
const { safeData: _safeData, safeMetas: _safeMetas } = guardDataKeys(
|
|
110
|
+
const { safeData: _safeData, safeMetas: _safeMetas } = guardDataKeys(remoteDataSource, rawFields);
|
|
83
111
|
safeData = _safeData;
|
|
84
112
|
safeMetas = _safeMetas;
|
|
85
113
|
}
|
|
@@ -87,7 +115,7 @@ const App = observer<IGWProps>(function App(props) {
|
|
|
87
115
|
safeData,
|
|
88
116
|
safeMetas,
|
|
89
117
|
};
|
|
90
|
-
}, [rawFields,
|
|
118
|
+
}, [rawFields, remoteDataSource, computation, fieldKeyGuard]);
|
|
91
119
|
|
|
92
120
|
// use as an embeding module, use outside datasource from props.
|
|
93
121
|
useEffect(() => {
|
|
@@ -106,6 +134,14 @@ const App = observer<IGWProps>(function App(props) {
|
|
|
106
134
|
}
|
|
107
135
|
}, [spec, safeDataset]);
|
|
108
136
|
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
if (computation) {
|
|
139
|
+
vizStore.setComputationFunction(computation);
|
|
140
|
+
} else {
|
|
141
|
+
vizStore.setComputationFunction(getComputation(commonStore.currentDataset.dataSource));
|
|
142
|
+
}
|
|
143
|
+
}, [vizStore, computation ?? commonStore.currentDataset.dataSource]);
|
|
144
|
+
|
|
109
145
|
const darkMode = useCurrentMediaTheme(dark);
|
|
110
146
|
|
|
111
147
|
const rendererRef = useRef<IReactVegaHandler>(null);
|
|
@@ -130,6 +166,9 @@ const App = observer<IGWProps>(function App(props) {
|
|
|
130
166
|
style={{ marginTop: '0em', borderTop: 'none' }}
|
|
131
167
|
className="m-4 p-4 border border-gray-200 dark:border-gray-700"
|
|
132
168
|
>
|
|
169
|
+
{enhanceAPI?.features?.askviz && (
|
|
170
|
+
<AskViz api={typeof enhanceAPI.features.askviz === 'string' ? enhanceAPI.features.askviz : ''} headers={enhanceAPI?.header} />
|
|
171
|
+
)}
|
|
133
172
|
<VisualSettings rendererHandler={rendererRef} darkModePreference={dark} exclude={toolbar?.exclude} extra={toolbar?.extra} />
|
|
134
173
|
<CodeExport />
|
|
135
174
|
<VisualConfig />
|
|
@@ -156,7 +195,7 @@ const App = observer<IGWProps>(function App(props) {
|
|
|
156
195
|
// }}
|
|
157
196
|
>
|
|
158
197
|
{datasets.length > 0 && (
|
|
159
|
-
<ReactiveRenderer ref={rendererRef} themeKey={themeKey} dark={dark} />
|
|
198
|
+
<ReactiveRenderer ref={rendererRef} themeKey={themeKey} dark={dark} computationFunction={vizStore.computationFuction} />
|
|
160
199
|
)}
|
|
161
200
|
{/* {vizEmbededMenu.show && (
|
|
162
201
|
<ClickMenu x={vizEmbededMenu.position[0]} y={vizEmbededMenu.position[1]}>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { observer } from 'mobx-react-lite';
|
|
2
|
+
import React, { useCallback, useState } from 'react';
|
|
3
|
+
import { useGlobalStore } from '../../store';
|
|
4
|
+
import { PaperAirplaneIcon } from '@heroicons/react/24/outline';
|
|
5
|
+
import Spinner from '../spinner';
|
|
6
|
+
import { IViewField } from '../../interfaces';
|
|
7
|
+
import { VisSpecWithHistory } from '../../models/visSpecHistory';
|
|
8
|
+
import { visSpecDecoder, forwardVisualConfigs } from '../../utils/save';
|
|
9
|
+
|
|
10
|
+
type VEGALite = any;
|
|
11
|
+
|
|
12
|
+
const api = import.meta.env.DEV ? 'http://localhost:2023/api/vis/text2gw' : 'https://enhanceai.kanaries.net/api/vis/text2gw'
|
|
13
|
+
|
|
14
|
+
async function vizQuery(api: string, metas: IViewField[], query: string, headers: Record<string, string>) {
|
|
15
|
+
const res = await fetch(api, {
|
|
16
|
+
headers: {
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
...headers,
|
|
19
|
+
},
|
|
20
|
+
credentials: 'include',
|
|
21
|
+
method: 'POST',
|
|
22
|
+
body: JSON.stringify({
|
|
23
|
+
metas,
|
|
24
|
+
messages: [
|
|
25
|
+
{
|
|
26
|
+
role: 'user',
|
|
27
|
+
content: query,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
32
|
+
const result: {
|
|
33
|
+
success: boolean;
|
|
34
|
+
data: VEGALite;
|
|
35
|
+
message?: string;
|
|
36
|
+
} = await res.json();
|
|
37
|
+
if (result.success) {
|
|
38
|
+
return result.data;
|
|
39
|
+
} else {
|
|
40
|
+
throw new Error(result.message);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const AskViz: React.FC<{api?: string; headers?: Record<string, string>}> = (props) => {
|
|
45
|
+
const [query, setQuery] = useState<string>('');
|
|
46
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
47
|
+
const { vizStore } = useGlobalStore();
|
|
48
|
+
|
|
49
|
+
const allFields = vizStore.allFields;
|
|
50
|
+
|
|
51
|
+
const startQuery = useCallback(() => {
|
|
52
|
+
setLoading(true);
|
|
53
|
+
vizQuery(props.api ?? api, allFields, query, props.headers ?? {})
|
|
54
|
+
.then((data) => {
|
|
55
|
+
vizStore.visList.push(new VisSpecWithHistory(visSpecDecoder(forwardVisualConfigs([data]))[0]));
|
|
56
|
+
vizStore.selectVisualization(vizStore.visList.length - 1);
|
|
57
|
+
// const liteGW = parseGW(spec);
|
|
58
|
+
// vizStore.renderSpec(liteGW);
|
|
59
|
+
})
|
|
60
|
+
.finally(() => {
|
|
61
|
+
setLoading(false);
|
|
62
|
+
});
|
|
63
|
+
}, [query, allFields]);
|
|
64
|
+
return (
|
|
65
|
+
<div className="right-0 flex">
|
|
66
|
+
<input
|
|
67
|
+
type="text"
|
|
68
|
+
className="rounded-l-md px-4 block w-full border-0 py-1.5 text-gray-900 dark:text-gray-50 shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 placeholder:text-gray-400 focus:ring-1 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6 dark:bg-gray-900"
|
|
69
|
+
placeholder="What visualization your want to draw from the dataset"
|
|
70
|
+
value={query}
|
|
71
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
72
|
+
onKeyDown={(e) => {
|
|
73
|
+
if (e.key === 'Enter' && loading === false && query.length > 0) {
|
|
74
|
+
startQuery();
|
|
75
|
+
}
|
|
76
|
+
}}
|
|
77
|
+
disabled={loading || allFields.length === 0}
|
|
78
|
+
/>
|
|
79
|
+
<button
|
|
80
|
+
type="button"
|
|
81
|
+
className="flex items-center grow-0 rounded-r-md bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 px-4 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
82
|
+
disabled={loading || query.length === 0 || allFields.length === 0}
|
|
83
|
+
onClick={startQuery}
|
|
84
|
+
>
|
|
85
|
+
Ask
|
|
86
|
+
{!loading && <PaperAirplaneIcon className="w-4 ml-1" />}
|
|
87
|
+
{loading && <Spinner />}
|
|
88
|
+
</button>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export default observer(AskViz);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Specification } from "../../interfaces";
|
|
2
|
+
|
|
3
|
+
export function parseGW(vlSpec: any): Specification {
|
|
4
|
+
const spec: Specification = {};
|
|
5
|
+
if (vlSpec.encoding && vlSpec.mark) {
|
|
6
|
+
spec.geomType = [vlSpec.mark];
|
|
7
|
+
spec.position = [];
|
|
8
|
+
if (vlSpec.encoding.x && vlSpec.encoding.x.field) {
|
|
9
|
+
spec.position.push(vlSpec.encoding.x.field);
|
|
10
|
+
if (vlSpec.encoding.x.aggregate) {
|
|
11
|
+
spec.aggregate = true;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
if (vlSpec.encoding.y && vlSpec.encoding.y.field) {
|
|
15
|
+
spec.position.push(vlSpec.encoding.y.field);
|
|
16
|
+
if (vlSpec.encoding.y.aggregate) {
|
|
17
|
+
spec.aggregate = true;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
spec.facets = [];
|
|
21
|
+
if (vlSpec.encoding.row && vlSpec.encoding.row) {
|
|
22
|
+
spec.facets.push(vlSpec.encoding.row);
|
|
23
|
+
}
|
|
24
|
+
if (vlSpec.encoding.column && vlSpec.encoding.column) {
|
|
25
|
+
spec.facets.push(vlSpec.encoding.column);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
['color', 'opacity', 'shape', 'size'].forEach((ch) => {
|
|
29
|
+
if (vlSpec.encoding[ch] && vlSpec.encoding[ch].field) {
|
|
30
|
+
spec[ch] = [vlSpec.encoding[ch].field];
|
|
31
|
+
if (vlSpec.encoding[ch].aggregate) {
|
|
32
|
+
spec.aggregate = true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return spec;
|
|
38
|
+
}
|
|
@@ -1,16 +1,24 @@
|
|
|
1
|
-
import React, { useMemo, useState } from 'react';
|
|
1
|
+
import React, { useMemo, useState, useRef, useEffect } from 'react';
|
|
2
2
|
import styled from 'styled-components';
|
|
3
|
-
import {
|
|
3
|
+
import { observer } from 'mobx-react-lite';
|
|
4
|
+
import type { IMutField, IRow, DataSet, IComputationFunction } from '../../interfaces';
|
|
4
5
|
import { useTranslation } from 'react-i18next';
|
|
6
|
+
import LoadingLayer from "../loadingLayer";
|
|
7
|
+
import { useComputationFunc } from "../../renderer/hooks";
|
|
8
|
+
import { dataReadRawServer } from "../../computation/serverComputation";
|
|
5
9
|
import Pagination from './pagination';
|
|
6
10
|
import { ChevronUpDownIcon } from '@heroicons/react/24/outline';
|
|
7
11
|
import DropdownContext from '../dropdownContext';
|
|
8
12
|
|
|
9
13
|
interface DataTableProps {
|
|
14
|
+
/** page limit */
|
|
10
15
|
size?: number;
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
/** total count of rows */
|
|
17
|
+
total: number;
|
|
18
|
+
dataset: DataSet;
|
|
19
|
+
computation?: IComputationFunction;
|
|
13
20
|
onMetaChange: (fid: string, fIndex: number, meta: Partial<IMutField>) => void;
|
|
21
|
+
loading?: boolean;
|
|
14
22
|
}
|
|
15
23
|
const Container = styled.div`
|
|
16
24
|
overflow-x: auto;
|
|
@@ -110,9 +118,11 @@ const getHeaderKey = (f: wrapMutField) => {
|
|
|
110
118
|
};
|
|
111
119
|
|
|
112
120
|
const DataTable: React.FC<DataTableProps> = (props) => {
|
|
113
|
-
const { size = 10,
|
|
121
|
+
const { size = 10, onMetaChange, dataset, computation, total, loading: statLoading } = props;
|
|
114
122
|
const [pageIndex, setPageIndex] = useState(0);
|
|
115
123
|
const { t } = useTranslation();
|
|
124
|
+
const defaultComputation = useComputationFunc();
|
|
125
|
+
const computationFunction = computation ?? defaultComputation;
|
|
116
126
|
|
|
117
127
|
const analyticTypeList = useMemo<{ value: string; label: string }[]>(() => {
|
|
118
128
|
return ANALYTIC_TYPE_LIST.map((at) => ({
|
|
@@ -129,18 +139,49 @@ const DataTable: React.FC<DataTableProps> = (props) => {
|
|
|
129
139
|
}, []);
|
|
130
140
|
|
|
131
141
|
const from = pageIndex * size;
|
|
132
|
-
const to = Math.min((pageIndex + 1) * size,
|
|
142
|
+
const to = Math.min((pageIndex + 1) * size - 1, total - 1);
|
|
143
|
+
|
|
144
|
+
const [rows, setRows] = useState<IRow[]>([]);
|
|
145
|
+
const [dataLoading, setDataLoading] = useState(false);
|
|
146
|
+
const taskIdRef = useRef(0);
|
|
147
|
+
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (statLoading) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
setDataLoading(true);
|
|
153
|
+
const taskId = ++taskIdRef.current;
|
|
154
|
+
dataReadRawServer(computationFunction, size, pageIndex).then(data => {
|
|
155
|
+
if (taskId === taskIdRef.current) {
|
|
156
|
+
setDataLoading(false);
|
|
157
|
+
setRows(data);
|
|
158
|
+
}
|
|
159
|
+
}).catch(err => {
|
|
160
|
+
if (taskId === taskIdRef.current) {
|
|
161
|
+
console.error(err);
|
|
162
|
+
setDataLoading(false);
|
|
163
|
+
setRows([]);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
return () => {
|
|
167
|
+
taskIdRef.current++;
|
|
168
|
+
};
|
|
169
|
+
}, [computationFunction, pageIndex, size]);
|
|
170
|
+
|
|
171
|
+
const loading = statLoading || dataLoading;
|
|
172
|
+
|
|
173
|
+
const metas = dataset.rawFields;
|
|
133
174
|
|
|
134
175
|
const headers = useMemo(() => getHeaders(metas), [metas]);
|
|
135
176
|
|
|
136
177
|
return (
|
|
137
|
-
<Container className="rounded border-gray-200 dark:border-gray-700 border">
|
|
178
|
+
<Container className="rounded border-gray-200 dark:border-gray-700 border relative">
|
|
138
179
|
<Pagination
|
|
139
|
-
total={
|
|
180
|
+
total={total}
|
|
140
181
|
from={from + 1}
|
|
141
182
|
to={to + 1}
|
|
142
183
|
onNext={() => {
|
|
143
|
-
setPageIndex(Math.min(Math.ceil(
|
|
184
|
+
setPageIndex(Math.min(Math.ceil(total / size) - 1, pageIndex + 1));
|
|
144
185
|
}}
|
|
145
186
|
onPrev={() => {
|
|
146
187
|
setPageIndex(Math.max(0, pageIndex - 1));
|
|
@@ -227,7 +268,7 @@ const DataTable: React.FC<DataTableProps> = (props) => {
|
|
|
227
268
|
))}
|
|
228
269
|
</thead>
|
|
229
270
|
<tbody className="divide-y divide-gray-100 dark:divide-gray-700 bg-white dark:bg-zinc-900">
|
|
230
|
-
{
|
|
271
|
+
{rows.map((row, index) => (
|
|
231
272
|
<tr
|
|
232
273
|
className={
|
|
233
274
|
'divide-x divide-gray-200 dark:divide-gray-700 ' +
|
|
@@ -250,8 +291,9 @@ const DataTable: React.FC<DataTableProps> = (props) => {
|
|
|
250
291
|
))}
|
|
251
292
|
</tbody>
|
|
252
293
|
</table>
|
|
294
|
+
{loading && <LoadingLayer />}
|
|
253
295
|
</Container>
|
|
254
296
|
);
|
|
255
297
|
};
|
|
256
298
|
|
|
257
|
-
export default DataTable;
|
|
299
|
+
export default observer(DataTable);
|
|
@@ -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={
|
|
15
|
+
value={innerValue > 0 ? innerValue : 0}
|
|
14
16
|
min="1"
|
|
15
17
|
max="50"
|
|
16
|
-
disabled={
|
|
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
|
-
|
|
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={
|
|
31
|
+
checked={innerValue > 0}
|
|
30
32
|
onChange={(e) => {
|
|
31
|
-
|
|
33
|
+
setInnerValue(e.target.checked ? 30 : -1);
|
|
32
34
|
}}
|
|
33
35
|
></input>
|
|
34
|
-
{`${t('limit')}${
|
|
36
|
+
{`${t('limit')}${innerValue > 0 ? `: ${innerValue}` : ''}`}
|
|
35
37
|
</output>
|
|
36
38
|
</div>
|
|
37
39
|
);
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { makeAutoObservable, observable } from 'mobx';
|
|
2
2
|
import { INestNode } from './inteface';
|
|
3
|
-
import { IAggQuery } from '../../lib/interfaces';
|
|
4
|
-
import { queryView } from '../../lib/viewQuery';
|
|
5
3
|
import { IField, IRow } from '../../interfaces';
|
|
6
4
|
import React, { createContext, useContext, useEffect } from 'react';
|
|
7
|
-
import { getMeaAggKey } from '../../utils';
|
|
8
5
|
|
|
9
6
|
class PivotTableStore {
|
|
10
7
|
public leftTree: INestNode | null = null;
|
|
@@ -30,19 +27,6 @@ class PivotTableStore {
|
|
|
30
27
|
this.topTree = null;
|
|
31
28
|
this.viewData = [];
|
|
32
29
|
}
|
|
33
|
-
public async queryData(leftQuery: IAggQuery, topQuery: IAggQuery) {
|
|
34
|
-
const viewQuery: IAggQuery = {
|
|
35
|
-
op: 'aggregate',
|
|
36
|
-
groupBy: leftQuery.groupBy.concat(topQuery.groupBy),
|
|
37
|
-
measures: leftQuery.measures.concat(topQuery.measures).map(mea => ({
|
|
38
|
-
field: mea.field,
|
|
39
|
-
agg: mea.agg,
|
|
40
|
-
asFieldKey: getMeaAggKey(mea.field, mea.agg)
|
|
41
|
-
}))
|
|
42
|
-
};
|
|
43
|
-
const viewData = queryView(this.dataSource, this.metas, viewQuery);
|
|
44
|
-
this.viewData = viewData;
|
|
45
|
-
}
|
|
46
30
|
}
|
|
47
31
|
|
|
48
32
|
const initStore = new PivotTableStore();
|
|
@@ -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(
|
|
27
|
+
value={Math.sqrt(innerWidth / 1000)}
|
|
26
28
|
min="0"
|
|
27
29
|
max="1"
|
|
28
30
|
step="0.01"
|
|
29
31
|
onChange={(e) => {
|
|
30
|
-
|
|
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")}: ${
|
|
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(
|
|
45
|
+
value={Math.sqrt(innerHeight / 1000)}
|
|
44
46
|
min="0"
|
|
45
47
|
max="1"
|
|
46
48
|
step="0.01"
|
|
47
49
|
onChange={(e) => {
|
|
48
|
-
|
|
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")}: ${
|
|
54
|
+
{`${t("height")}: ${innerHeight}`}
|
|
53
55
|
</output>
|
|
54
56
|
</div>
|
|
55
57
|
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export default function Spinner() {
|
|
4
|
+
return (
|
|
5
|
+
<svg className="animate-spin ml-2 mr-2 h-5 w-5 text-white" viewBox="0 0 24 24">
|
|
6
|
+
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
|
|
7
|
+
<path
|
|
8
|
+
className="opacity-75"
|
|
9
|
+
fill="currentColor"
|
|
10
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
11
|
+
></path>
|
|
12
|
+
</svg>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -20,7 +20,7 @@ export default function Toggle(props: ToggleProps) {
|
|
|
20
20
|
checked={enabled}
|
|
21
21
|
onChange={onChange}
|
|
22
22
|
className={classNames(
|
|
23
|
-
enabled ? 'bg-indigo-600' : 'bg-gray-200',
|
|
23
|
+
enabled ? 'bg-indigo-600' : 'bg-gray-200 dark:bg-gray-700',
|
|
24
24
|
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2'
|
|
25
25
|
)}
|
|
26
26
|
>
|
|
@@ -33,7 +33,7 @@ export default function Toggle(props: ToggleProps) {
|
|
|
33
33
|
/>
|
|
34
34
|
</Switch>
|
|
35
35
|
<Switch.Label as="span" className="ml-3 text-sm">
|
|
36
|
-
<span className="font-medium
|
|
36
|
+
<span className="font-medium">{label}</span>
|
|
37
37
|
</Switch.Label>
|
|
38
38
|
</Switch.Group>
|
|
39
39
|
);
|