@kanaries/graphic-walker 0.2.15 → 0.2.17
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 +4 -3
- package/dist/assets/transform.worker-5d54ff09.js.map +1 -0
- package/dist/assets/viewQuery.worker-ffefc111.js.map +1 -0
- package/dist/components/codeExport/index.d.ts +3 -0
- package/dist/components/loadingLayer.d.ts +2 -0
- package/dist/components/tabs/defaultTab.d.ts +1 -0
- package/dist/components/tabs/editableTab.d.ts +1 -2
- package/dist/dataSource/dataSelection/config.d.ts +1 -0
- package/dist/dataSource/dataSelection/utils.d.ts +2 -0
- package/dist/dataSource/index.d.ts +0 -1
- package/dist/datasets/tmp/test.json +1 -0
- package/dist/fields/encodeFields/singleEncodeEditor.d.ts +4 -4
- package/dist/graphic-walker.es.js +29228 -33650
- package/dist/graphic-walker.es.js.map +1 -1
- package/dist/graphic-walker.umd.js +218 -256
- package/dist/graphic-walker.umd.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/interfaces.d.ts +52 -17
- package/dist/lib/execExp.d.ts +8 -0
- package/dist/lib/inferMeta.d.ts +2 -9
- package/dist/lib/insights/explainByChildren.d.ts +16 -0
- package/dist/lib/insights/explainBySelection.d.ts +5 -0
- package/dist/lib/insights/explainValue.d.ts +2 -0
- package/dist/lib/insights/utils.d.ts +11 -0
- package/dist/lib/interfaces.d.ts +23 -0
- package/dist/lib/op/aggregate.d.ts +3 -0
- package/dist/lib/op/bin.d.ts +3 -0
- package/dist/lib/op/fold.d.ts +3 -0
- package/dist/lib/op/stat.d.ts +8 -0
- package/dist/lib/viewQuery.d.ts +4 -0
- package/dist/models/visSpecHistory.d.ts +2 -0
- package/dist/renderer/index.d.ts +8 -7
- package/dist/renderer/specRenderer.d.ts +13 -0
- package/dist/services.d.ts +5 -31
- package/dist/store/commonStore.d.ts +6 -0
- package/dist/store/index.d.ts +3 -2
- package/dist/store/visualSpecStore.d.ts +11 -5
- package/dist/utils/autoMark.d.ts +1 -1
- package/dist/utils/dataPrep.d.ts +3 -2
- package/dist/utils/index.d.ts +3 -5
- package/dist/utils/save.d.ts +1 -2
- package/dist/vis/react-vega.d.ts +2 -22
- package/dist/vis/spec/aggregate.d.ts +4 -0
- package/dist/vis/spec/encode.d.ts +20 -0
- package/dist/vis/spec/field.d.ts +2 -0
- package/dist/vis/spec/mark.d.ts +7 -0
- package/dist/vis/spec/stack.d.ts +4 -0
- package/dist/vis/spec/tooltip.d.ts +4 -0
- package/dist/vis/spec/view.d.ts +73 -0
- package/dist/workers/transform.d.ts +2 -0
- package/package.json +5 -6
- package/src/App.tsx +56 -66
- package/src/components/codeExport/index.tsx +114 -0
- package/src/components/dataTable/index.tsx +10 -10
- package/src/components/loadingLayer.tsx +7 -0
- package/src/components/tabs/defaultTab.tsx +4 -2
- package/src/components/tabs/editableTab.tsx +74 -39
- package/src/dataSource/dataSelection/config.ts +11 -0
- package/src/dataSource/dataSelection/csvData.tsx +71 -39
- package/src/dataSource/dataSelection/gwFile.tsx +2 -2
- package/src/dataSource/dataSelection/utils.ts +28 -0
- package/src/dataSource/index.tsx +0 -17
- package/src/dataSource/utils.ts +8 -3
- package/src/fields/aestheticFields.tsx +1 -2
- package/src/fields/datasetFields/meaFields.tsx +12 -4
- package/src/fields/encodeFields/singleEncodeEditor.tsx +11 -12
- package/src/fields/fieldsContext.tsx +1 -0
- package/src/index.css +4 -4
- package/src/index.tsx +22 -22
- package/src/interfaces.ts +85 -49
- package/src/lib/execExp.ts +147 -0
- package/src/lib/inferMeta.ts +26 -29
- package/src/lib/insights/explainByChildren.ts +50 -0
- package/src/lib/insights/explainBySelection.ts +47 -0
- package/src/lib/insights/explainValue.ts +30 -0
- package/src/lib/insights/utils.ts +21 -0
- package/src/lib/interfaces.ts +33 -0
- package/src/lib/op/aggregate.ts +49 -0
- package/src/lib/op/bin.ts +25 -0
- package/src/lib/op/fold.ts +17 -0
- package/src/lib/op/stat.ts +46 -0
- package/src/lib/viewQuery.ts +22 -0
- package/src/locales/en-US.json +6 -3
- package/src/locales/i18n.ts +0 -1
- package/src/locales/ja-JP.json +4 -2
- package/src/locales/zh-CN.json +6 -3
- package/src/main.tsx +1 -1
- package/src/models/visSpecHistory.ts +14 -0
- package/src/renderer/index.tsx +58 -126
- package/src/renderer/specRenderer.tsx +121 -0
- package/src/segments/segmentNav.tsx +3 -16
- package/src/segments/visNav.tsx +17 -6
- package/src/services.ts +101 -67
- package/src/store/commonStore.ts +14 -9
- package/src/store/index.tsx +11 -4
- package/src/store/visualSpecStore.ts +89 -52
- package/src/utils/autoMark.ts +1 -1
- package/src/utils/dataPrep.ts +25 -2
- package/src/utils/index.ts +16 -17
- package/src/utils/normalization.ts +3 -1
- package/src/utils/save.ts +1 -2
- package/src/vis/react-vega.tsx +9 -340
- package/src/vis/spec/aggregate.ts +13 -0
- package/src/vis/spec/encode.ts +70 -0
- package/src/vis/spec/field.ts +10 -0
- package/src/vis/spec/mark.ts +30 -0
- package/src/vis/spec/stack.ts +11 -0
- package/src/vis/spec/tooltip.ts +16 -0
- package/src/vis/spec/view.ts +136 -0
- package/src/vis/theme.ts +12 -0
- package/src/visualSettings/index.tsx +10 -1
- package/src/workers/transform.ts +12 -0
- package/src/workers/transform.worker.js +13 -0
- package/src/workers/viewQuery.worker.js +16 -0
- package/dist/assets/explainer.worker-8428eb12.js.map +0 -1
- package/dist/dataSource/pannel.d.ts +0 -5
- package/dist/insightBoard/index.d.ts +0 -3
- package/dist/insightBoard/mainBoard.d.ts +0 -11
- package/dist/insightBoard/radioGroupButtons.d.ts +0 -12
- package/dist/insightBoard/selectionSpec.d.ts +0 -13
- package/dist/insightBoard/std2vegaSpec.d.ts +0 -12
- package/dist/insightBoard/utils.d.ts +0 -8
- package/dist/insights.d.ts +0 -61
- package/src/dataSource/pannel.tsx +0 -71
- package/src/insightBoard/index.tsx +0 -31
- package/src/insightBoard/mainBoard.tsx +0 -224
- package/src/insightBoard/radioGroupButtons.tsx +0 -57
- package/src/insightBoard/selectionSpec.ts +0 -113
- package/src/insightBoard/std2vegaSpec.ts +0 -184
- package/src/insightBoard/utils.ts +0 -32
- package/src/insights.ts +0 -408
- package/src/workers/explainer.worker.js +0 -76
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kanaries/graphic-walker",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.17",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"dev:front_end": "vite --host",
|
|
6
6
|
"dev": "npm run dev:front_end",
|
|
@@ -28,14 +28,15 @@
|
|
|
28
28
|
},
|
|
29
29
|
"prettier": {
|
|
30
30
|
"tabWidth": 4,
|
|
31
|
-
"printWidth": 120
|
|
31
|
+
"printWidth": 120,
|
|
32
|
+
"singleQuote": true
|
|
32
33
|
},
|
|
33
34
|
"types": "./dist/index.d.ts",
|
|
34
35
|
"dependencies": {
|
|
35
36
|
"@headlessui/react": "^1.7.12",
|
|
36
37
|
"@heroicons/react": "^2.0.8",
|
|
37
38
|
"@kanaries/react-beautiful-dnd": "0.0.1",
|
|
38
|
-
"@kanaries/web-data-loader": "0.1.
|
|
39
|
+
"@kanaries/web-data-loader": "^0.1.7",
|
|
39
40
|
"autoprefixer": "^10.3.5",
|
|
40
41
|
"i18next": "^21.9.1",
|
|
41
42
|
"i18next-browser-languagedetector": "^6.1.5",
|
|
@@ -46,15 +47,13 @@
|
|
|
46
47
|
"postinstall-postinstall": "^2.1.0",
|
|
47
48
|
"re-resizable": "^6.9.8",
|
|
48
49
|
"react-i18next": "^11.18.6",
|
|
49
|
-
"react-json-view": "^1.21.3",
|
|
50
50
|
"react-shadow": "^20.0.0",
|
|
51
51
|
"rxjs": "^7.3.0",
|
|
52
52
|
"tailwindcss": "^3.2.4",
|
|
53
53
|
"uuid": "^8.3.2",
|
|
54
54
|
"vega": "^5.22.1",
|
|
55
55
|
"vega-embed": "^6.21.0",
|
|
56
|
-
"vega-lite": "^5.6.0"
|
|
57
|
-
"visual-insights": "0.7.15"
|
|
56
|
+
"vega-lite": "^5.6.0"
|
|
58
57
|
},
|
|
59
58
|
"devDependencies": {
|
|
60
59
|
"@rollup/plugin-typescript": "^8.2.5",
|
package/src/App.tsx
CHANGED
|
@@ -1,28 +1,23 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
-
import
|
|
21
|
-
import FilterField from "./fields/filterField";
|
|
22
|
-
import { guardDataKeys } from "./utils/dataPrep";
|
|
23
|
-
import SegmentNav from "./segments/segmentNav";
|
|
24
|
-
import DatasetConfig from "./dataSource/datasetConfig";
|
|
25
|
-
import { useCurrentMediaTheme } from "./utils/media";
|
|
1
|
+
import React, { useEffect, useRef, useMemo } from 'react';
|
|
2
|
+
import { observer } from 'mobx-react-lite';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { IDarkMode, IMutField, IRow, ISegmentKey, IThemeKey, Specification } from './interfaces';
|
|
5
|
+
import type { IReactVegaHandler } from './vis/react-vega';
|
|
6
|
+
import VisualSettings from './visualSettings';
|
|
7
|
+
import PosFields from './fields/posFields';
|
|
8
|
+
import AestheticFields from './fields/aestheticFields';
|
|
9
|
+
import DatasetFields from './fields/datasetFields/index';
|
|
10
|
+
import ReactiveRenderer from './renderer/index';
|
|
11
|
+
import DataSourceSegment from './dataSource/index';
|
|
12
|
+
import { IGlobalStore, useGlobalStore } from './store';
|
|
13
|
+
import VisNav from './segments/visNav';
|
|
14
|
+
import { mergeLocaleRes, setLocaleLanguage } from './locales/i18n';
|
|
15
|
+
import FilterField from './fields/filterField';
|
|
16
|
+
import { guardDataKeys } from './utils/dataPrep';
|
|
17
|
+
import SegmentNav from './segments/segmentNav';
|
|
18
|
+
import DatasetConfig from './dataSource/datasetConfig';
|
|
19
|
+
import { useCurrentMediaTheme } from './utils/media';
|
|
20
|
+
import CodeExport from './components/codeExport';
|
|
26
21
|
|
|
27
22
|
export interface IGWProps {
|
|
28
23
|
dataSource?: IRow[];
|
|
@@ -39,14 +34,15 @@ export interface IGWProps {
|
|
|
39
34
|
/** @default "vega" */
|
|
40
35
|
themeKey?: IThemeKey;
|
|
41
36
|
dark?: IDarkMode;
|
|
37
|
+
storeRef?: React.MutableRefObject<IGlobalStore | null>;
|
|
42
38
|
}
|
|
43
39
|
|
|
44
|
-
const App = observer<IGWProps>(function App
|
|
40
|
+
const App = observer<IGWProps>(function App(props) {
|
|
45
41
|
const {
|
|
46
42
|
dataSource = [],
|
|
47
43
|
rawFields = [],
|
|
48
44
|
spec,
|
|
49
|
-
i18nLang =
|
|
45
|
+
i18nLang = 'en-US',
|
|
50
46
|
i18nResources,
|
|
51
47
|
hideDataSourceConfig,
|
|
52
48
|
fieldKeyGuard = true,
|
|
@@ -54,9 +50,8 @@ const App = observer<IGWProps>(function App (props) {
|
|
|
54
50
|
dark = 'media',
|
|
55
51
|
} = props;
|
|
56
52
|
const { commonStore, vizStore } = useGlobalStore();
|
|
57
|
-
const [insightReady, setInsightReady] = useState<boolean>(true);
|
|
58
53
|
|
|
59
|
-
const {
|
|
54
|
+
const { datasets, segmentKey } = commonStore;
|
|
60
55
|
|
|
61
56
|
const { t, i18n } = useTranslation();
|
|
62
57
|
const curLang = i18n.language;
|
|
@@ -91,54 +86,45 @@ const App = observer<IGWProps>(function App (props) {
|
|
|
91
86
|
useEffect(() => {
|
|
92
87
|
if (safeDataset.safeData.length > 0 && safeDataset.safeMetas.length > 0) {
|
|
93
88
|
commonStore.addAndUseDS({
|
|
94
|
-
name:
|
|
89
|
+
name: 'context dataset',
|
|
95
90
|
dataSource: safeDataset.safeData,
|
|
96
91
|
rawFields: safeDataset.safeMetas,
|
|
97
92
|
});
|
|
98
93
|
}
|
|
99
94
|
}, [safeDataset]);
|
|
100
95
|
|
|
101
|
-
// do preparation analysis work when using a new dataset
|
|
102
96
|
useEffect(() => {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
setInsightReady(false);
|
|
106
|
-
preAnalysis({
|
|
107
|
-
dataSource: ds.dataSource,
|
|
108
|
-
fields: toJS(ds.rawFields),
|
|
109
|
-
}).then(() => {
|
|
110
|
-
setInsightReady(true);
|
|
111
|
-
|
|
112
|
-
if (spec) {
|
|
113
|
-
vizStore.renderSpec(spec);
|
|
114
|
-
}
|
|
115
|
-
});
|
|
97
|
+
if (safeDataset.safeData.length > 0 && safeDataset.safeMetas.length > 0 && spec) {
|
|
98
|
+
vizStore.renderSpec(spec);
|
|
116
99
|
}
|
|
117
|
-
|
|
118
|
-
destroyWorker();
|
|
119
|
-
};
|
|
120
|
-
}, [currentDataset, spec]);
|
|
100
|
+
}, [spec, safeDataset]);
|
|
121
101
|
|
|
122
102
|
const darkMode = useCurrentMediaTheme(dark);
|
|
123
103
|
|
|
124
104
|
const rendererRef = useRef<IReactVegaHandler>(null);
|
|
125
105
|
|
|
126
106
|
return (
|
|
127
|
-
<div
|
|
107
|
+
<div
|
|
108
|
+
className={`${
|
|
109
|
+
darkMode === 'dark' ? 'dark' : ''
|
|
110
|
+
} App font-sans bg-white dark:bg-zinc-900 dark:text-white m-0 p-0`}
|
|
111
|
+
>
|
|
128
112
|
{/* <div className="grow-0">
|
|
129
113
|
<PageNav />
|
|
130
114
|
</div> */}
|
|
131
115
|
<div className="bg-white dark:bg-zinc-900 dark:text-white">
|
|
132
|
-
{!hideDataSourceConfig && <DataSourceSegment
|
|
116
|
+
{!hideDataSourceConfig && <DataSourceSegment />}
|
|
133
117
|
<div className="px-2 mx-2">
|
|
134
118
|
<SegmentNav />
|
|
135
|
-
{
|
|
136
|
-
segmentKey === ISegmentKey.vis && <VisNav />
|
|
137
|
-
}
|
|
119
|
+
{segmentKey === ISegmentKey.vis && <VisNav />}
|
|
138
120
|
</div>
|
|
139
121
|
{segmentKey === ISegmentKey.vis && (
|
|
140
|
-
<div
|
|
122
|
+
<div
|
|
123
|
+
style={{ marginTop: '0em', borderTop: 'none' }}
|
|
124
|
+
className="m-4 p-4 border border-gray-200 dark:border-gray-700"
|
|
125
|
+
>
|
|
141
126
|
<VisualSettings rendererHandler={rendererRef} darkModePreference={dark} />
|
|
127
|
+
<CodeExport />
|
|
142
128
|
<div className="md:grid md:grid-cols-12 xl:grid-cols-6">
|
|
143
129
|
<div className="md:col-span-3 xl:col-span-1">
|
|
144
130
|
<DatasetFields />
|
|
@@ -153,17 +139,18 @@ const App = observer<IGWProps>(function App (props) {
|
|
|
153
139
|
</div>
|
|
154
140
|
<div
|
|
155
141
|
className="m-0.5 p-1 border border-gray-200 dark:border-gray-700"
|
|
156
|
-
style={{ minHeight:
|
|
157
|
-
onMouseLeave={() => {
|
|
158
|
-
|
|
159
|
-
}}
|
|
160
|
-
onClick={() => {
|
|
161
|
-
|
|
162
|
-
}}
|
|
142
|
+
style={{ minHeight: '600px', overflow: 'auto' }}
|
|
143
|
+
// onMouseLeave={() => {
|
|
144
|
+
// vizEmbededMenu.show && commonStore.closeEmbededMenu();
|
|
145
|
+
// }}
|
|
146
|
+
// onClick={() => {
|
|
147
|
+
// vizEmbededMenu.show && commonStore.closeEmbededMenu();
|
|
148
|
+
// }}
|
|
163
149
|
>
|
|
164
|
-
{datasets.length > 0 &&
|
|
165
|
-
|
|
166
|
-
|
|
150
|
+
{datasets.length > 0 && (
|
|
151
|
+
<ReactiveRenderer ref={rendererRef} themeKey={themeKey} dark={dark} />
|
|
152
|
+
)}
|
|
153
|
+
{/* {vizEmbededMenu.show && (
|
|
167
154
|
<ClickMenu x={vizEmbededMenu.position[0]} y={vizEmbededMenu.position[1]}>
|
|
168
155
|
<div
|
|
169
156
|
className="flex items-center whitespace-nowrap py-1 px-4 hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer"
|
|
@@ -178,14 +165,17 @@ const App = observer<IGWProps>(function App (props) {
|
|
|
178
165
|
<LightBulbIcon className="ml-1 w-3 flex-grow-0 flex-shrink-0" />
|
|
179
166
|
</div>
|
|
180
167
|
</ClickMenu>
|
|
181
|
-
)}
|
|
168
|
+
)} */}
|
|
182
169
|
</div>
|
|
183
170
|
</div>
|
|
184
171
|
</div>
|
|
185
172
|
</div>
|
|
186
173
|
)}
|
|
187
174
|
{segmentKey === ISegmentKey.data && (
|
|
188
|
-
<div
|
|
175
|
+
<div
|
|
176
|
+
className="m-4 p-4 border border-gray-200 dark:border-gray-700"
|
|
177
|
+
style={{ marginTop: '0em', borderTop: 'none' }}
|
|
178
|
+
>
|
|
189
179
|
<DatasetConfig />
|
|
190
180
|
</div>
|
|
191
181
|
)}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import React, { useEffect, useState } from "react";
|
|
2
|
+
import Modal from "../modal";
|
|
3
|
+
import { observer } from "mobx-react-lite";
|
|
4
|
+
import { useGlobalStore } from "../../store";
|
|
5
|
+
import DefaultButton from "../button/default";
|
|
6
|
+
import PrimaryButton from "../button/primary";
|
|
7
|
+
import { useTranslation } from "react-i18next";
|
|
8
|
+
import DefaultTab, { ITabOption } from "../tabs/defaultTab";
|
|
9
|
+
|
|
10
|
+
const syntaxHighlight = (json: any) => {
|
|
11
|
+
if (typeof json != "string") {
|
|
12
|
+
json = JSON.stringify(json, undefined, 4);
|
|
13
|
+
}
|
|
14
|
+
json = json
|
|
15
|
+
.replace(/&/g, "&")
|
|
16
|
+
.replace(/</g, "<")
|
|
17
|
+
.replace(/>/g, ">")
|
|
18
|
+
.replace(/\n/g, "<br>")
|
|
19
|
+
.replace(/\t/g, " ")
|
|
20
|
+
.replace(/\s/g, " ");
|
|
21
|
+
return json.replace(
|
|
22
|
+
/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
|
|
23
|
+
function (match) {
|
|
24
|
+
var cls = "text-sky-500"; // number
|
|
25
|
+
if (/^"/.test(match)) {
|
|
26
|
+
if (/:$/.test(match)) {
|
|
27
|
+
cls = "text-purple-500"; // key
|
|
28
|
+
} else {
|
|
29
|
+
cls = "text-emerald-500"; // string
|
|
30
|
+
}
|
|
31
|
+
} else if (/true|false/.test(match)) {
|
|
32
|
+
cls = "text-blue-500";
|
|
33
|
+
} else if (/null/.test(match)) {
|
|
34
|
+
cls = "text-sky-500";
|
|
35
|
+
}
|
|
36
|
+
return '<span class="' + cls + '">' + match + "</span>";
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const CodeExport: React.FC = observer((props) => {
|
|
42
|
+
const { commonStore, vizStore } = useGlobalStore();
|
|
43
|
+
const { showCodeExportPanel } = commonStore;
|
|
44
|
+
const { t } = useTranslation();
|
|
45
|
+
const [tabKey, setTabKey] = useState<string>("graphic-walker");
|
|
46
|
+
const [code, setCode] = useState<any>("");
|
|
47
|
+
|
|
48
|
+
const specTabs: ITabOption[] = [
|
|
49
|
+
{
|
|
50
|
+
key: "graphic-walker",
|
|
51
|
+
label: "Graphic-Walker",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
key: "vega-lite",
|
|
55
|
+
label: "Vega-Lite",
|
|
56
|
+
disabled: true
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (showCodeExportPanel) {
|
|
62
|
+
if (tabKey === "graphic-walker") {
|
|
63
|
+
const res = vizStore.exportViewSpec();
|
|
64
|
+
setCode(res);
|
|
65
|
+
} else {
|
|
66
|
+
setCode("vega code");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}, [tabKey, showCodeExportPanel]);
|
|
70
|
+
return (
|
|
71
|
+
<Modal
|
|
72
|
+
show={showCodeExportPanel}
|
|
73
|
+
onClose={() => {
|
|
74
|
+
commonStore.setShowCodeExportPanel(false);
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
<div>
|
|
78
|
+
<h1>Code Export</h1>
|
|
79
|
+
<DefaultTab
|
|
80
|
+
tabs={specTabs}
|
|
81
|
+
selectedKey={tabKey}
|
|
82
|
+
onSelected={(k) => {
|
|
83
|
+
setTabKey(k as string);
|
|
84
|
+
}}
|
|
85
|
+
/>
|
|
86
|
+
{tabKey === "graphic-walker" && (
|
|
87
|
+
<div className="text-sm px-6 max-h-56 overflow-auto">
|
|
88
|
+
<div dangerouslySetInnerHTML={{ __html: syntaxHighlight(code) }} />
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
91
|
+
<div className="mt-4 flex justify-start">
|
|
92
|
+
<PrimaryButton
|
|
93
|
+
// text={t("actions.confirm")}
|
|
94
|
+
className="mr-2 px-6"
|
|
95
|
+
text="Copy to Clipboard"
|
|
96
|
+
onClick={() => {
|
|
97
|
+
navigator.clipboard.writeText(JSON.stringify(code));
|
|
98
|
+
commonStore.setShowCodeExportPanel(false);
|
|
99
|
+
}}
|
|
100
|
+
/>
|
|
101
|
+
<DefaultButton
|
|
102
|
+
text={t("actions.cancel")}
|
|
103
|
+
className="mr-2 px-6"
|
|
104
|
+
onClick={() => {
|
|
105
|
+
commonStore.setShowCodeExportPanel(false);
|
|
106
|
+
}}
|
|
107
|
+
/>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
</Modal>
|
|
111
|
+
);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
export default CodeExport;
|
|
@@ -48,15 +48,15 @@ function getHeaderClassNames(field: IMutField) {
|
|
|
48
48
|
function getSemanticColors(field: IMutField): string {
|
|
49
49
|
switch (field.semanticType) {
|
|
50
50
|
case "nominal":
|
|
51
|
-
return "bg-sky-100 text-sky-800";
|
|
51
|
+
return "border border-transparent bg-sky-100 text-sky-800 dark:bg-sky-900 dark:text-sky-100 dark:border-sky-600";
|
|
52
52
|
case "ordinal":
|
|
53
|
-
return "bg-indigo-100 text-indigo-800";
|
|
53
|
+
return "border border-transparent bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-100 dark:border-indigo-600";
|
|
54
54
|
case "quantitative":
|
|
55
|
-
return "bg-purple-100 text-purple-800";
|
|
55
|
+
return "border border-transparent bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-100 dark:border-purple-600";
|
|
56
56
|
case "temporal":
|
|
57
|
-
return "bg-yellow-100 text-yellow-800";
|
|
57
|
+
return "border border-transparent bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-100 dark:border-yellow-600";
|
|
58
58
|
default:
|
|
59
|
-
return "bg-gray-400";
|
|
59
|
+
return "border border-transparent bg-gray-400";
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
@@ -97,7 +97,7 @@ const DataTable: React.FC<DataTableProps> = (props) => {
|
|
|
97
97
|
/>
|
|
98
98
|
<table className="min-w-full divide-y">
|
|
99
99
|
<thead className="bg-gray-50 dark:bg-gray-900">
|
|
100
|
-
<tr className="divide-x divide-gray-200 dark:divide-gray-
|
|
100
|
+
<tr className="divide-x divide-gray-200 dark:divide-gray-700">
|
|
101
101
|
{metas.map((field, fIndex) => (
|
|
102
102
|
<th key={field.fid} className={""}>
|
|
103
103
|
<div
|
|
@@ -152,9 +152,9 @@ const DataTable: React.FC<DataTableProps> = (props) => {
|
|
|
152
152
|
))}
|
|
153
153
|
</tr>
|
|
154
154
|
</thead>
|
|
155
|
-
<tbody className="divide-y divide-gray-100 dark:divide-gray-
|
|
156
|
-
{data.slice(from, to).map((row, index) => (
|
|
157
|
-
<tr className={"divide-x divide-gray-200 dark:divide-gray-
|
|
155
|
+
<tbody className="divide-y divide-gray-100 dark:divide-gray-700 bg-white dark:bg-zinc-900">
|
|
156
|
+
{data.slice(from, to + 1).map((row, index) => (
|
|
157
|
+
<tr className={"divide-x divide-gray-200 dark:divide-gray-700 " + (index % 2 ? "bg-gray-50 dark:bg-gray-900" : "")} key={index}>
|
|
158
158
|
{metas.map((field) => (
|
|
159
159
|
<td
|
|
160
160
|
key={field.fid + index}
|
|
@@ -163,7 +163,7 @@ const DataTable: React.FC<DataTableProps> = (props) => {
|
|
|
163
163
|
" whitespace-nowrap py-2 pl-4 pr-3 text-xs text-gray-500 dark:text-gray-300 sm:pl-6"
|
|
164
164
|
}
|
|
165
165
|
>
|
|
166
|
-
{row[field.fid]}
|
|
166
|
+
{`${row[field.fid]}`}
|
|
167
167
|
</td>
|
|
168
168
|
))}
|
|
169
169
|
</tr>
|
|
@@ -7,6 +7,7 @@ function classNames(...classes: string[]) {
|
|
|
7
7
|
export interface ITabOption {
|
|
8
8
|
label: string | ReactElement;
|
|
9
9
|
key: string;
|
|
10
|
+
disabled?: boolean;
|
|
10
11
|
}
|
|
11
12
|
interface DefaultProps {
|
|
12
13
|
tabs: ITabOption[];
|
|
@@ -26,14 +27,15 @@ export default function Default(props: DefaultProps) {
|
|
|
26
27
|
role="tab"
|
|
27
28
|
tabIndex={0}
|
|
28
29
|
onClick={() => {
|
|
29
|
-
onSelected(tab.key, tabIndex)
|
|
30
|
+
!tab.disabled && onSelected(tab.key, tabIndex)
|
|
30
31
|
}}
|
|
31
32
|
key={tab.key}
|
|
32
33
|
className={classNames(
|
|
33
34
|
tab.key === selectedKey
|
|
34
35
|
? 'border-indigo-500 text-indigo-600 dark:border-indigo-400 dark:text-indigo-300'
|
|
35
36
|
: 'border-transparent text-gray-500 hover:text-gray-700 dark:hover:text-gray-200 hover:border-gray-300 dark:text-gray-400',
|
|
36
|
-
'whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm cursor-pointer'
|
|
37
|
+
'whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm cursor-pointer',
|
|
38
|
+
tab.disabled ? 'opacity-50 cursor-not-allowed' : ''
|
|
37
39
|
)}
|
|
38
40
|
>{tab.label}</span>
|
|
39
41
|
))}
|
|
@@ -1,74 +1,109 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useState } from "react";
|
|
2
2
|
import { useTranslation } from "react-i18next";
|
|
3
|
-
|
|
3
|
+
import { PencilSquareIcon } from "@heroicons/react/24/outline";
|
|
4
|
+
import Modal from "../modal";
|
|
5
|
+
import { unstable_batchedUpdates } from "react-dom";
|
|
6
|
+
import DefaultButton from "../button/default";
|
|
7
|
+
import PrimaryButton from "../button/primary";
|
|
4
8
|
|
|
5
9
|
function classNames(...classes: string[]) {
|
|
6
|
-
return classes.filter(Boolean).join(
|
|
10
|
+
return classes.filter(Boolean).join(" ");
|
|
7
11
|
}
|
|
8
12
|
|
|
9
13
|
export interface ITabOption {
|
|
10
14
|
label: string;
|
|
11
15
|
key: string;
|
|
12
|
-
|
|
16
|
+
editable?: boolean;
|
|
13
17
|
}
|
|
14
18
|
interface EditableTabsProps {
|
|
15
19
|
tabs: ITabOption[];
|
|
16
20
|
selectedKey: string;
|
|
17
21
|
onSelected: (selectedKey: string, index: number) => void;
|
|
18
|
-
allowEdit?: boolean;
|
|
19
22
|
onEditLabel?: (label: string, index: number) => void;
|
|
20
23
|
}
|
|
21
24
|
export default function EditableTabs(props: EditableTabsProps) {
|
|
22
|
-
const { tabs, selectedKey, onSelected,
|
|
23
|
-
const [
|
|
25
|
+
const { tabs, selectedKey, onSelected, onEditLabel } = props;
|
|
26
|
+
const [editingIndex, setEditingIndex] = useState<number>(-1);
|
|
27
|
+
const [name, setName] = useState<string>("");
|
|
24
28
|
const { t } = useTranslation();
|
|
25
29
|
|
|
26
|
-
const clearEditStatus = useCallback(() => {
|
|
27
|
-
setEditList(new Array(tabs.length).fill(false))
|
|
28
|
-
}, [tabs.length]);
|
|
29
|
-
|
|
30
|
-
useEffect(() => {
|
|
31
|
-
clearEditStatus();
|
|
32
|
-
}, [clearEditStatus]);
|
|
33
|
-
|
|
34
30
|
return (
|
|
35
|
-
<div className="border-b border-gray-200 dark:border-gray-700 overflow-x-auto overflow-y-hidden"
|
|
31
|
+
<div className="border-b border-gray-200 dark:border-gray-700 overflow-x-auto overflow-y-hidden">
|
|
32
|
+
<Modal
|
|
33
|
+
show={editingIndex > -1}
|
|
34
|
+
onClose={() => {
|
|
35
|
+
setEditingIndex(-1);
|
|
36
|
+
}}
|
|
37
|
+
>
|
|
38
|
+
<div>
|
|
39
|
+
<span className="block text-sm font-medium leading-6">{t('main.tablist.chart_name')}</span>
|
|
40
|
+
<div className="mt-2">
|
|
41
|
+
<input
|
|
42
|
+
value={name}
|
|
43
|
+
onChange={(e) => {
|
|
44
|
+
setName(e.target.value);
|
|
45
|
+
}}
|
|
46
|
+
type="text"
|
|
47
|
+
name="text"
|
|
48
|
+
className="block w-full rounded-md border-0 px-2 py-1.5 bg-transparent shadow-sm ring-1 ring-inset ring-gray-300 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
<div className="mt-4 flex justify-end">
|
|
52
|
+
<DefaultButton
|
|
53
|
+
className="mr-2"
|
|
54
|
+
text={t("actions.cancel")}
|
|
55
|
+
onClick={() => {
|
|
56
|
+
unstable_batchedUpdates(() => {
|
|
57
|
+
setEditingIndex(-1);
|
|
58
|
+
setName("");
|
|
59
|
+
});
|
|
60
|
+
}}
|
|
61
|
+
/>
|
|
62
|
+
<PrimaryButton
|
|
63
|
+
text={t("actions.confirm")}
|
|
64
|
+
onClick={() => {
|
|
65
|
+
unstable_batchedUpdates(() => {
|
|
66
|
+
onEditLabel && onEditLabel(name, editingIndex);
|
|
67
|
+
setEditingIndex(-1);
|
|
68
|
+
setName("");
|
|
69
|
+
});
|
|
70
|
+
}}
|
|
71
|
+
/>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</Modal>
|
|
36
75
|
<nav className="-mb-px flex h-8 border-gray-200 dark:border-gray-700" role="tablist" aria-label="Tabs">
|
|
37
76
|
{tabs.map((tab, tabIndex) => (
|
|
38
77
|
<span
|
|
39
78
|
role="tab"
|
|
40
79
|
tabIndex={0}
|
|
41
|
-
dangerouslySetInnerHTML={{
|
|
42
|
-
|
|
43
|
-
}}
|
|
80
|
+
// dangerouslySetInnerHTML={{
|
|
81
|
+
// __html: tab.label
|
|
82
|
+
// }}
|
|
44
83
|
onClick={() => {
|
|
45
|
-
onSelected(tab.key, tabIndex)
|
|
46
|
-
}}
|
|
47
|
-
onDoubleClick={() => {
|
|
48
|
-
setEditList(v => {
|
|
49
|
-
const nv = [...v];
|
|
50
|
-
nv[tabIndex] = true;
|
|
51
|
-
return nv
|
|
52
|
-
})
|
|
53
|
-
}}
|
|
54
|
-
contentEditable={editList[tabIndex]}
|
|
55
|
-
onInput={(e) => {
|
|
56
|
-
onEditLabel && onEditLabel(`${e.currentTarget.textContent}`, tabIndex)
|
|
57
|
-
}}
|
|
58
|
-
onKeyDown={(e) => {
|
|
59
|
-
if (e.key === 'Enter') {
|
|
60
|
-
clearEditStatus();
|
|
61
|
-
e.preventDefault();
|
|
62
|
-
}
|
|
84
|
+
onSelected(tab.key, tabIndex);
|
|
63
85
|
}}
|
|
64
86
|
key={tab.key}
|
|
65
87
|
className={classNames(
|
|
66
88
|
tab.key === selectedKey
|
|
67
89
|
? "border rounded-t"
|
|
68
90
|
: "text-gray-500 dark:text-gray-400 hover:text-gray-700 hover:bg-gray-50 dark:hover:text-gray-200 dark:hover:bg-gray-800",
|
|
69
|
-
"whitespace-nowrap border-gray-200 dark:border-gray-700 py-1 px-2 pr-6 text-sm cursor-
|
|
91
|
+
"whitespace-nowrap border-gray-200 dark:border-gray-700 py-1 px-2 pr-6 text-sm cursor-default dark:text-white"
|
|
92
|
+
)}
|
|
93
|
+
>
|
|
94
|
+
{tab.label}{" "}
|
|
95
|
+
{tab.key === selectedKey && tab.editable && (
|
|
96
|
+
<PencilSquareIcon
|
|
97
|
+
className="w-3 inline cursor-pointer"
|
|
98
|
+
onClick={() => {
|
|
99
|
+
unstable_batchedUpdates(() => {
|
|
100
|
+
setEditingIndex(tabIndex);
|
|
101
|
+
setName(tab.label);
|
|
102
|
+
});
|
|
103
|
+
}}
|
|
104
|
+
/>
|
|
70
105
|
)}
|
|
71
|
-
|
|
106
|
+
</span>
|
|
72
107
|
))}
|
|
73
108
|
</nav>
|
|
74
109
|
</div>
|
|
@@ -25,4 +25,15 @@ export const charsetOptions: IDropdownSelectOption[] = [
|
|
|
25
25
|
label: 'GB18030',
|
|
26
26
|
value: 'GB18030',
|
|
27
27
|
},
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
export const SUPPORTED_FILE_TYPES: IDropdownSelectOption[] = [
|
|
31
|
+
{
|
|
32
|
+
label: 'CSV',
|
|
33
|
+
value: 'csv',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
label: 'JSON',
|
|
37
|
+
value: 'json',
|
|
38
|
+
}
|
|
28
39
|
]
|