@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.
Files changed (132) hide show
  1. package/dist/App.d.ts +4 -3
  2. package/dist/assets/transform.worker-5d54ff09.js.map +1 -0
  3. package/dist/assets/viewQuery.worker-ffefc111.js.map +1 -0
  4. package/dist/components/codeExport/index.d.ts +3 -0
  5. package/dist/components/loadingLayer.d.ts +2 -0
  6. package/dist/components/tabs/defaultTab.d.ts +1 -0
  7. package/dist/components/tabs/editableTab.d.ts +1 -2
  8. package/dist/dataSource/dataSelection/config.d.ts +1 -0
  9. package/dist/dataSource/dataSelection/utils.d.ts +2 -0
  10. package/dist/dataSource/index.d.ts +0 -1
  11. package/dist/datasets/tmp/test.json +1 -0
  12. package/dist/fields/encodeFields/singleEncodeEditor.d.ts +4 -4
  13. package/dist/graphic-walker.es.js +29228 -33650
  14. package/dist/graphic-walker.es.js.map +1 -1
  15. package/dist/graphic-walker.umd.js +218 -256
  16. package/dist/graphic-walker.umd.js.map +1 -1
  17. package/dist/index.d.ts +3 -3
  18. package/dist/interfaces.d.ts +52 -17
  19. package/dist/lib/execExp.d.ts +8 -0
  20. package/dist/lib/inferMeta.d.ts +2 -9
  21. package/dist/lib/insights/explainByChildren.d.ts +16 -0
  22. package/dist/lib/insights/explainBySelection.d.ts +5 -0
  23. package/dist/lib/insights/explainValue.d.ts +2 -0
  24. package/dist/lib/insights/utils.d.ts +11 -0
  25. package/dist/lib/interfaces.d.ts +23 -0
  26. package/dist/lib/op/aggregate.d.ts +3 -0
  27. package/dist/lib/op/bin.d.ts +3 -0
  28. package/dist/lib/op/fold.d.ts +3 -0
  29. package/dist/lib/op/stat.d.ts +8 -0
  30. package/dist/lib/viewQuery.d.ts +4 -0
  31. package/dist/models/visSpecHistory.d.ts +2 -0
  32. package/dist/renderer/index.d.ts +8 -7
  33. package/dist/renderer/specRenderer.d.ts +13 -0
  34. package/dist/services.d.ts +5 -31
  35. package/dist/store/commonStore.d.ts +6 -0
  36. package/dist/store/index.d.ts +3 -2
  37. package/dist/store/visualSpecStore.d.ts +11 -5
  38. package/dist/utils/autoMark.d.ts +1 -1
  39. package/dist/utils/dataPrep.d.ts +3 -2
  40. package/dist/utils/index.d.ts +3 -5
  41. package/dist/utils/save.d.ts +1 -2
  42. package/dist/vis/react-vega.d.ts +2 -22
  43. package/dist/vis/spec/aggregate.d.ts +4 -0
  44. package/dist/vis/spec/encode.d.ts +20 -0
  45. package/dist/vis/spec/field.d.ts +2 -0
  46. package/dist/vis/spec/mark.d.ts +7 -0
  47. package/dist/vis/spec/stack.d.ts +4 -0
  48. package/dist/vis/spec/tooltip.d.ts +4 -0
  49. package/dist/vis/spec/view.d.ts +73 -0
  50. package/dist/workers/transform.d.ts +2 -0
  51. package/package.json +5 -6
  52. package/src/App.tsx +56 -66
  53. package/src/components/codeExport/index.tsx +114 -0
  54. package/src/components/dataTable/index.tsx +10 -10
  55. package/src/components/loadingLayer.tsx +7 -0
  56. package/src/components/tabs/defaultTab.tsx +4 -2
  57. package/src/components/tabs/editableTab.tsx +74 -39
  58. package/src/dataSource/dataSelection/config.ts +11 -0
  59. package/src/dataSource/dataSelection/csvData.tsx +71 -39
  60. package/src/dataSource/dataSelection/gwFile.tsx +2 -2
  61. package/src/dataSource/dataSelection/utils.ts +28 -0
  62. package/src/dataSource/index.tsx +0 -17
  63. package/src/dataSource/utils.ts +8 -3
  64. package/src/fields/aestheticFields.tsx +1 -2
  65. package/src/fields/datasetFields/meaFields.tsx +12 -4
  66. package/src/fields/encodeFields/singleEncodeEditor.tsx +11 -12
  67. package/src/fields/fieldsContext.tsx +1 -0
  68. package/src/index.css +4 -4
  69. package/src/index.tsx +22 -22
  70. package/src/interfaces.ts +85 -49
  71. package/src/lib/execExp.ts +147 -0
  72. package/src/lib/inferMeta.ts +26 -29
  73. package/src/lib/insights/explainByChildren.ts +50 -0
  74. package/src/lib/insights/explainBySelection.ts +47 -0
  75. package/src/lib/insights/explainValue.ts +30 -0
  76. package/src/lib/insights/utils.ts +21 -0
  77. package/src/lib/interfaces.ts +33 -0
  78. package/src/lib/op/aggregate.ts +49 -0
  79. package/src/lib/op/bin.ts +25 -0
  80. package/src/lib/op/fold.ts +17 -0
  81. package/src/lib/op/stat.ts +46 -0
  82. package/src/lib/viewQuery.ts +22 -0
  83. package/src/locales/en-US.json +6 -3
  84. package/src/locales/i18n.ts +0 -1
  85. package/src/locales/ja-JP.json +4 -2
  86. package/src/locales/zh-CN.json +6 -3
  87. package/src/main.tsx +1 -1
  88. package/src/models/visSpecHistory.ts +14 -0
  89. package/src/renderer/index.tsx +58 -126
  90. package/src/renderer/specRenderer.tsx +121 -0
  91. package/src/segments/segmentNav.tsx +3 -16
  92. package/src/segments/visNav.tsx +17 -6
  93. package/src/services.ts +101 -67
  94. package/src/store/commonStore.ts +14 -9
  95. package/src/store/index.tsx +11 -4
  96. package/src/store/visualSpecStore.ts +89 -52
  97. package/src/utils/autoMark.ts +1 -1
  98. package/src/utils/dataPrep.ts +25 -2
  99. package/src/utils/index.ts +16 -17
  100. package/src/utils/normalization.ts +3 -1
  101. package/src/utils/save.ts +1 -2
  102. package/src/vis/react-vega.tsx +9 -340
  103. package/src/vis/spec/aggregate.ts +13 -0
  104. package/src/vis/spec/encode.ts +70 -0
  105. package/src/vis/spec/field.ts +10 -0
  106. package/src/vis/spec/mark.ts +30 -0
  107. package/src/vis/spec/stack.ts +11 -0
  108. package/src/vis/spec/tooltip.ts +16 -0
  109. package/src/vis/spec/view.ts +136 -0
  110. package/src/vis/theme.ts +12 -0
  111. package/src/visualSettings/index.tsx +10 -1
  112. package/src/workers/transform.ts +12 -0
  113. package/src/workers/transform.worker.js +13 -0
  114. package/src/workers/viewQuery.worker.js +16 -0
  115. package/dist/assets/explainer.worker-8428eb12.js.map +0 -1
  116. package/dist/dataSource/pannel.d.ts +0 -5
  117. package/dist/insightBoard/index.d.ts +0 -3
  118. package/dist/insightBoard/mainBoard.d.ts +0 -11
  119. package/dist/insightBoard/radioGroupButtons.d.ts +0 -12
  120. package/dist/insightBoard/selectionSpec.d.ts +0 -13
  121. package/dist/insightBoard/std2vegaSpec.d.ts +0 -12
  122. package/dist/insightBoard/utils.d.ts +0 -8
  123. package/dist/insights.d.ts +0 -61
  124. package/src/dataSource/pannel.tsx +0 -71
  125. package/src/insightBoard/index.tsx +0 -31
  126. package/src/insightBoard/mainBoard.tsx +0 -224
  127. package/src/insightBoard/radioGroupButtons.tsx +0 -57
  128. package/src/insightBoard/selectionSpec.ts +0 -113
  129. package/src/insightBoard/std2vegaSpec.ts +0 -184
  130. package/src/insightBoard/utils.ts +0 -32
  131. package/src/insights.ts +0 -408
  132. package/src/workers/explainer.worker.js +0 -76
@@ -0,0 +1,2 @@
1
+ import { IField, IRow } from "../interfaces";
2
+ export declare function transformData(data: IRow[], columns: IField[]): IRow[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kanaries/graphic-walker",
3
- "version": "0.2.15",
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.5",
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, { useState, useEffect, useRef, useMemo } from "react";
2
- import { Specification } from "visual-insights";
3
- import { observer } from "mobx-react-lite";
4
- import { LightBulbIcon } from "@heroicons/react/24/outline";
5
- import { toJS } from "mobx";
6
- import { useTranslation } from "react-i18next";
7
- import { IDarkMode, IMutField, IRow, ISegmentKey, IThemeKey } from "./interfaces";
8
- import type { IReactVegaHandler } from "./vis/react-vega";
9
- import VisualSettings from "./visualSettings";
10
- import ClickMenu from "./components/clickMenu";
11
- import InsightBoard from "./insightBoard/index";
12
- import PosFields from "./fields/posFields";
13
- import AestheticFields from "./fields/aestheticFields";
14
- import DatasetFields from "./fields/datasetFields/index";
15
- import ReactiveRenderer from "./renderer/index";
16
- import DataSourceSegment from "./dataSource/index";
17
- import { useGlobalStore } from "./store";
18
- import { preAnalysis, destroyWorker } from "./services";
19
- import VisNav from "./segments/visNav";
20
- import { mergeLocaleRes, setLocaleLanguage } from "./locales/i18n";
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 (props) {
40
+ const App = observer<IGWProps>(function App(props) {
45
41
  const {
46
42
  dataSource = [],
47
43
  rawFields = [],
48
44
  spec,
49
- i18nLang = "en-US",
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 { currentDataset, datasets, vizEmbededMenu, segmentKey } = commonStore;
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: "context dataset",
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
- const ds = currentDataset;
104
- if (ds && ds.dataSource.length > 0 && ds.rawFields.length > 0) {
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
- return () => {
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 className={`${darkMode === 'dark' ? 'dark' : ''} App font-sans bg-white dark:bg-zinc-900 dark:text-white m-0 p-0`}>
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 preWorkDone={insightReady} />}
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 style={{ marginTop: "0em", borderTop: "none" }} className="m-4 p-4 border border-gray-200 dark:border-gray-700">
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: "600px", overflow: "auto" }}
157
- onMouseLeave={() => {
158
- vizEmbededMenu.show && commonStore.closeEmbededMenu();
159
- }}
160
- onClick={() => {
161
- vizEmbededMenu.show && commonStore.closeEmbededMenu();
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 && <ReactiveRenderer ref={rendererRef} themeKey={themeKey} dark={dark} />}
165
- <InsightBoard />
166
- {vizEmbededMenu.show && (
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 className="m-4 p-4 border border-gray-200 dark:border-gray-700" style={{ marginTop: "0em", borderTop: "none" }}>
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, "&amp;")
16
+ .replace(/</g, "&lt;")
17
+ .replace(/>/g, "&gt;")
18
+ .replace(/\n/g, "<br>")
19
+ .replace(/\t/g, "&nbsp;&nbsp;&nbsp;&nbsp;")
20
+ .replace(/\s/g, "&nbsp;&nbsp;");
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-600">
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-600 bg-white dark:bg-zinc-900">
156
- {data.slice(from, to).map((row, index) => (
157
- <tr className={"divide-x divide-gray-200 dark:divide-gray-600 " + (index % 2 ? "bg-gray-50 dark:bg-gray-800" : "")} key={index}>
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>
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+
3
+ export default function LoadingLayer () {
4
+ return <div className="bg-gray-100/50 dark:bg-gray-700/50 absolute top-0 left-0 right-0 bottom-0 z-50 flex items-center justify-center">
5
+ Loading...
6
+ </div>
7
+ }
@@ -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, { useCallback, useEffect, useState } from "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
- options?: Record<string, any>;
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, allowEdit, onEditLabel } = props;
23
- const [editList, setEditList] = useState<boolean[]>([]);
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" onMouseLeave={clearEditStatus}>
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
- __html: t(tab.label, tab.options)
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-pointer dark:text-white"
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
  ]