@kanaries/graphic-walker 0.2.14 → 0.2.16

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 (128) hide show
  1. package/dist/App.d.ts +5 -2
  2. package/dist/assets/explainer.worker-8428eb12.js.map +1 -1
  3. package/dist/assets/transform.worker-5d54ff09.js.map +1 -0
  4. package/dist/assets/viewQuery.worker-ffefc111.js.map +1 -0
  5. package/dist/components/callout.d.ts +2 -0
  6. package/dist/components/codeExport/index.d.ts +3 -0
  7. package/dist/components/loadingLayer.d.ts +2 -0
  8. package/dist/components/tabs/defaultTab.d.ts +1 -0
  9. package/dist/components/tabs/editableTab.d.ts +1 -2
  10. package/dist/components/toolbar/components.d.ts +4 -1
  11. package/dist/components/toolbar/index.d.ts +2 -0
  12. package/dist/components/toolbar/toolbar-item.d.ts +3 -0
  13. package/dist/components/tooltip.d.ts +2 -0
  14. package/dist/dataSource/dataSelection/config.d.ts +1 -0
  15. package/dist/dataSource/dataSelection/utils.d.ts +2 -0
  16. package/dist/datasets/tmp/test.json +1 -0
  17. package/dist/fields/components.d.ts +0 -1
  18. package/dist/fields/filterField/filterEditDialog.d.ts +1 -1
  19. package/dist/graphic-walker.es.js +23930 -23320
  20. package/dist/graphic-walker.es.js.map +1 -1
  21. package/dist/graphic-walker.umd.js +143 -273
  22. package/dist/graphic-walker.umd.js.map +1 -1
  23. package/dist/index.d.ts +3 -3
  24. package/dist/interfaces.d.ts +23 -1
  25. package/dist/lib/execExp.d.ts +8 -0
  26. package/dist/lib/interfaces.d.ts +22 -0
  27. package/dist/lib/op/aggregate.d.ts +3 -0
  28. package/dist/lib/op/bin.d.ts +3 -0
  29. package/dist/lib/op/fold.d.ts +3 -0
  30. package/dist/lib/op/stat.d.ts +8 -0
  31. package/dist/lib/viewQuery.d.ts +5 -0
  32. package/dist/models/visSpecHistory.d.ts +2 -0
  33. package/dist/renderer/index.d.ts +6 -3
  34. package/dist/renderer/specRenderer.d.ts +13 -0
  35. package/dist/services.d.ts +4 -1
  36. package/dist/store/commonStore.d.ts +6 -0
  37. package/dist/store/index.d.ts +3 -2
  38. package/dist/store/visualSpecStore.d.ts +11 -4
  39. package/dist/utils/dataPrep.d.ts +2 -0
  40. package/dist/utils/index.d.ts +3 -5
  41. package/dist/utils/media.d.ts +2 -1
  42. package/dist/utils/save.d.ts +1 -2
  43. package/dist/vis/react-vega.d.ts +4 -23
  44. package/dist/vis/spec/aggregate.d.ts +4 -0
  45. package/dist/vis/spec/encode.d.ts +19 -0
  46. package/dist/vis/spec/field.d.ts +2 -0
  47. package/dist/vis/spec/mark.d.ts +7 -0
  48. package/dist/vis/spec/stack.d.ts +4 -0
  49. package/dist/vis/spec/view.d.ts +67 -0
  50. package/dist/vis/theme.d.ts +36 -20
  51. package/dist/visualSettings/index.d.ts +2 -1
  52. package/dist/workers/transform.d.ts +2 -0
  53. package/package.json +4 -3
  54. package/src/App.tsx +23 -15
  55. package/src/components/callout.tsx +9 -7
  56. package/src/components/clickMenu.tsx +1 -7
  57. package/src/components/codeExport/index.tsx +114 -0
  58. package/src/components/dataTable/index.tsx +10 -10
  59. package/src/components/loadingLayer.tsx +7 -0
  60. package/src/components/modal.tsx +1 -15
  61. package/src/components/sizeSetting.tsx +2 -2
  62. package/src/components/tabs/defaultTab.tsx +4 -2
  63. package/src/components/tabs/editableTab.tsx +75 -40
  64. package/src/components/toolbar/components.tsx +8 -23
  65. package/src/components/toolbar/index.tsx +11 -4
  66. package/src/components/toolbar/toolbar-button.tsx +2 -1
  67. package/src/components/toolbar/toolbar-item.tsx +17 -12
  68. package/src/components/toolbar/toolbar-select-button.tsx +9 -13
  69. package/src/components/toolbar/toolbar-toggle-button.tsx +2 -1
  70. package/src/components/tooltip.tsx +10 -6
  71. package/src/dataSource/dataSelection/config.ts +11 -0
  72. package/src/dataSource/dataSelection/csvData.tsx +72 -40
  73. package/src/dataSource/dataSelection/gwFile.tsx +2 -2
  74. package/src/dataSource/dataSelection/utils.ts +28 -0
  75. package/src/dataSource/index.tsx +2 -3
  76. package/src/dataSource/utils.ts +8 -3
  77. package/src/fields/components.tsx +13 -50
  78. package/src/fields/datasetFields/index.tsx +3 -4
  79. package/src/fields/datasetFields/meaFields.tsx +12 -4
  80. package/src/fields/encodeFields/singleEncodeEditor.tsx +1 -1
  81. package/src/fields/filterField/filterEditDialog.tsx +63 -99
  82. package/src/fields/filterField/slider.tsx +1 -1
  83. package/src/index.css +4 -4
  84. package/src/index.tsx +22 -22
  85. package/src/insightBoard/mainBoard.tsx +9 -2
  86. package/src/interfaces.ts +30 -3
  87. package/src/lib/execExp.ts +147 -0
  88. package/src/lib/interfaces.ts +39 -0
  89. package/src/lib/op/aggregate.ts +49 -0
  90. package/src/lib/op/bin.ts +25 -0
  91. package/src/lib/op/fold.ts +17 -0
  92. package/src/lib/op/stat.ts +46 -0
  93. package/src/lib/viewQuery.ts +23 -0
  94. package/src/locales/en-US.json +8 -3
  95. package/src/locales/i18n.ts +7 -1
  96. package/src/locales/ja-JP.json +197 -0
  97. package/src/locales/zh-CN.json +8 -3
  98. package/src/main.tsx +1 -1
  99. package/src/models/visSpecHistory.ts +14 -0
  100. package/src/renderer/index.tsx +58 -101
  101. package/src/renderer/specRenderer.tsx +119 -0
  102. package/src/segments/segmentNav.tsx +3 -16
  103. package/src/segments/visNav.tsx +17 -6
  104. package/src/services.ts +37 -1
  105. package/src/store/commonStore.ts +14 -9
  106. package/src/store/index.tsx +11 -4
  107. package/src/store/visualSpecStore.ts +89 -50
  108. package/src/utils/dataPrep.ts +24 -0
  109. package/src/utils/index.ts +16 -17
  110. package/src/utils/media.ts +16 -11
  111. package/src/utils/normalization.ts +3 -1
  112. package/src/utils/save.ts +1 -2
  113. package/src/vis/react-vega.tsx +11 -332
  114. package/src/vis/spec/aggregate.ts +13 -0
  115. package/src/vis/spec/encode.ts +69 -0
  116. package/src/vis/spec/field.ts +10 -0
  117. package/src/vis/spec/mark.ts +30 -0
  118. package/src/vis/spec/stack.ts +11 -0
  119. package/src/vis/spec/view.ts +138 -0
  120. package/src/vis/theme.ts +35 -25
  121. package/src/visualSettings/index.tsx +22 -33
  122. package/src/workers/transform.ts +12 -0
  123. package/src/workers/transform.worker.js +13 -0
  124. package/src/workers/viewQuery.worker.js +16 -0
  125. package/dist/components/container.d.ts +0 -2
  126. package/dist/dataSource/pannel.d.ts +0 -5
  127. package/src/components/container.tsx +0 -25
  128. package/src/dataSource/pannel.tsx +0 -71
package/src/App.tsx CHANGED
@@ -4,10 +4,9 @@ import { observer } from "mobx-react-lite";
4
4
  import { LightBulbIcon } from "@heroicons/react/24/outline";
5
5
  import { toJS } from "mobx";
6
6
  import { useTranslation } from "react-i18next";
7
- import { IMutField, IRow, ISegmentKey } from "./interfaces";
7
+ import { IDarkMode, IMutField, IRow, ISegmentKey, IThemeKey } from "./interfaces";
8
8
  import type { IReactVegaHandler } from "./vis/react-vega";
9
9
  import VisualSettings from "./visualSettings";
10
- import { Container, NestContainer } from "./components/container";
11
10
  import ClickMenu from "./components/clickMenu";
12
11
  import InsightBoard from "./insightBoard/index";
13
12
  import PosFields from "./fields/posFields";
@@ -15,7 +14,7 @@ import AestheticFields from "./fields/aestheticFields";
15
14
  import DatasetFields from "./fields/datasetFields/index";
16
15
  import ReactiveRenderer from "./renderer/index";
17
16
  import DataSourceSegment from "./dataSource/index";
18
- import { useGlobalStore } from "./store";
17
+ import { IGlobalStore, useGlobalStore } from "./store";
19
18
  import { preAnalysis, destroyWorker } from "./services";
20
19
  import VisNav from "./segments/visNav";
21
20
  import { mergeLocaleRes, setLocaleLanguage } from "./locales/i18n";
@@ -23,6 +22,8 @@ import FilterField from "./fields/filterField";
23
22
  import { guardDataKeys } from "./utils/dataPrep";
24
23
  import SegmentNav from "./segments/segmentNav";
25
24
  import DatasetConfig from "./dataSource/datasetConfig";
25
+ import { useCurrentMediaTheme } from "./utils/media";
26
+ import CodeExport from "./components/codeExport";
26
27
 
27
28
  export interface IGWProps {
28
29
  dataSource?: IRow[];
@@ -37,7 +38,9 @@ export interface IGWProps {
37
38
  */
38
39
  fieldKeyGuard?: boolean;
39
40
  /** @default "vega" */
40
- themeKey?: 'vega' | 'g2';
41
+ themeKey?: IThemeKey;
42
+ dark?: IDarkMode;
43
+ storeRef?: React.MutableRefObject<IGlobalStore | null>;
41
44
  }
42
45
 
43
46
  const App = observer<IGWProps>(function App (props) {
@@ -50,6 +53,7 @@ const App = observer<IGWProps>(function App (props) {
50
53
  hideDataSourceConfig,
51
54
  fieldKeyGuard = true,
52
55
  themeKey = 'vega',
56
+ dark = 'media'
53
57
  } = props;
54
58
  const { commonStore, vizStore } = useGlobalStore();
55
59
  const [insightReady, setInsightReady] = useState<boolean>(true);
@@ -117,14 +121,16 @@ const App = observer<IGWProps>(function App (props) {
117
121
  };
118
122
  }, [currentDataset, spec]);
119
123
 
124
+ const darkMode = useCurrentMediaTheme(dark);
125
+
120
126
  const rendererRef = useRef<IReactVegaHandler>(null);
121
127
 
122
128
  return (
123
- <div className="App font-sans dark:bg-zinc-900 dark:text-white m-0 p-0">
129
+ <div className={`${darkMode === 'dark' ? 'dark' : ''} App font-sans bg-white dark:bg-zinc-900 dark:text-white m-0 p-0`}>
124
130
  {/* <div className="grow-0">
125
131
  <PageNav />
126
132
  </div> */}
127
- <div className="">
133
+ <div className="bg-white dark:bg-zinc-900 dark:text-white">
128
134
  {!hideDataSourceConfig && <DataSourceSegment preWorkDone={insightReady} />}
129
135
  <div className="px-2 mx-2">
130
136
  <SegmentNav />
@@ -133,8 +139,9 @@ const App = observer<IGWProps>(function App (props) {
133
139
  }
134
140
  </div>
135
141
  {segmentKey === ISegmentKey.vis && (
136
- <Container style={{ marginTop: "0em", borderTop: "none" }}>
137
- <VisualSettings rendererHandler={rendererRef} />
142
+ <div style={{ marginTop: "0em", borderTop: "none" }} className="m-4 p-4 border border-gray-200 dark:border-gray-700">
143
+ <VisualSettings rendererHandler={rendererRef} darkModePreference={dark} />
144
+ <CodeExport />
138
145
  <div className="md:grid md:grid-cols-12 xl:grid-cols-6">
139
146
  <div className="md:col-span-3 xl:col-span-1">
140
147
  <DatasetFields />
@@ -147,7 +154,8 @@ const App = observer<IGWProps>(function App (props) {
147
154
  <div>
148
155
  <PosFields />
149
156
  </div>
150
- <NestContainer
157
+ <div
158
+ className="m-0.5 p-1 border border-gray-200 dark:border-gray-700"
151
159
  style={{ minHeight: "600px", overflow: "auto" }}
152
160
  onMouseLeave={() => {
153
161
  vizEmbededMenu.show && commonStore.closeEmbededMenu();
@@ -156,12 +164,12 @@ const App = observer<IGWProps>(function App (props) {
156
164
  vizEmbededMenu.show && commonStore.closeEmbededMenu();
157
165
  }}
158
166
  >
159
- {datasets.length > 0 && <ReactiveRenderer ref={rendererRef} themeKey={themeKey} />}
167
+ {datasets.length > 0 && <ReactiveRenderer ref={rendererRef} themeKey={themeKey} dark={dark} />}
160
168
  <InsightBoard />
161
169
  {vizEmbededMenu.show && (
162
170
  <ClickMenu x={vizEmbededMenu.position[0]} y={vizEmbededMenu.position[1]}>
163
171
  <div
164
- className="flex items-center whitespace-nowrap py-1 px-4 hover:bg-gray-100"
172
+ className="flex items-center whitespace-nowrap py-1 px-4 hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer"
165
173
  onClick={() => {
166
174
  commonStore.closeEmbededMenu();
167
175
  commonStore.setShowInsightBoard(true);
@@ -174,15 +182,15 @@ const App = observer<IGWProps>(function App (props) {
174
182
  </div>
175
183
  </ClickMenu>
176
184
  )}
177
- </NestContainer>
185
+ </div>
178
186
  </div>
179
187
  </div>
180
- </Container>
188
+ </div>
181
189
  )}
182
190
  {segmentKey === ISegmentKey.data && (
183
- <Container style={{ marginTop: "0em", borderTop: "none" }}>
191
+ <div className="m-4 p-4 border border-gray-200 dark:border-gray-700" style={{ marginTop: "0em", borderTop: "none" }}>
184
192
  <DatasetConfig />
185
- </Container>
193
+ </div>
186
194
  )}
187
195
  </div>
188
196
  </div>
@@ -1,14 +1,17 @@
1
1
  import React, { memo, ReactNode, useContext, useEffect, useState } from "react";
2
2
  import { createPortal } from "react-dom";
3
3
  import styled from "styled-components";
4
+ import type { IDarkMode } from "../interfaces";
4
5
  import { ShadowDomContext } from "..";
6
+ import { useCurrentMediaTheme } from "../utils/media";
5
7
 
6
8
  export interface CalloutProps {
7
9
  target: string;
8
10
  children: ReactNode;
11
+ darkModePreference?: IDarkMode;
9
12
  }
10
13
 
11
- const Bubble = styled.div`
14
+ const Bubble = styled.div<{ dark: boolean }>`
12
15
  border-radius: 1px;
13
16
  transform: translate(-50%, 0);
14
17
  filter: drop-shadow(0 1.6px 1px rgba(0, 0, 0, 0.24)) drop-shadow(0 -1px 0.8px rgba(0, 0, 0, 0.19));
@@ -25,15 +28,12 @@ const Bubble = styled.div`
25
28
  width: 8px;
26
29
  height: 8px;
27
30
  transform: translate(-50%, -50%) rotate(45deg);
28
- background-color: #fff;
31
+ background-color: ${({ dark }) => dark ? "#000" : "#fff"};
29
32
  border-radius: 1px;
30
- @media (prefers-color-scheme: dark) {
31
- background-color: #000;
32
- }
33
33
  }
34
34
  `;
35
35
 
36
- const Callout = memo<CalloutProps>(function Callout({ target, children }) {
36
+ const Callout = memo<CalloutProps>(function Callout({ target, children, darkModePreference = 'media' }) {
37
37
  const shadowDomMeta = useContext(ShadowDomContext);
38
38
  const { root } = shadowDomMeta;
39
39
  const [pos, setPos] = useState<[number, number] | null>(null);
@@ -48,11 +48,13 @@ const Callout = memo<CalloutProps>(function Callout({ target, children }) {
48
48
  }
49
49
  }, [target, root]);
50
50
 
51
+ const darkMode = useCurrentMediaTheme(darkModePreference);
52
+
51
53
  return (
52
54
  root &&
53
55
  pos &&
54
56
  createPortal(
55
- <Bubble role="dialog" className="fixed bg-white dark:bg-zinc-900 z-50" style={{ left: pos[0], top: pos[1] + 4 }}>
57
+ <Bubble role="dialog" dark={darkMode === 'dark'} className="fixed bg-white dark:bg-zinc-900 z-50" style={{ left: pos[0], top: pos[1] + 4 }}>
56
58
  {children}
57
59
  </Bubble>,
58
60
  root
@@ -3,16 +3,10 @@ import styled from "styled-components";
3
3
 
4
4
  const MenuContainer = styled.div`
5
5
  min-width: 100px;
6
- background-color: #fff;
7
- border: 1px solid #f0f0f0;
8
6
  position: absolute;
9
7
  z-index: 99;
10
8
  cursor: pointer;
11
9
  padding: 4px;
12
- @media (prefers-color-scheme: dark) {
13
- background-color: #000;
14
- border: 1px solid #4b5563;
15
- }
16
10
  `;
17
11
  interface ClickMenuProps {
18
12
  x: number;
@@ -22,7 +16,7 @@ interface ClickMenuProps {
22
16
  const ClickMenu: React.FC<ClickMenuProps> = (props) => {
23
17
  const { x, y, children } = props;
24
18
  return (
25
- <MenuContainer className="shadow-lg text-sm" style={{ left: x + "px", top: y + "px" }}>
19
+ <MenuContainer className="shadow-lg text-sm bg-white border border-gray-100 dark:bg-black dark:border-gray-700" style={{ left: x + "px", top: y + "px" }}>
26
20
  {children}
27
21
  </MenuContainer>
28
22
  );
@@ -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
+ }
@@ -25,16 +25,6 @@ const Container = styled.div`
25
25
  }
26
26
  max-height: 800px;
27
27
  overflow: auto;
28
- > div.header {
29
- background-color: #f0f0f0;
30
- display: flex;
31
- padding: 12px;
32
- font-size: 14px;
33
- align-items: center;
34
- @media (prefers-color-scheme: dark) {
35
- background-color: #000;
36
- }
37
- }
38
28
  > div.container {
39
29
  padding: 0.5em 1em 1em 1em;
40
30
  }
@@ -42,10 +32,6 @@ const Container = styled.div`
42
32
  left: 50%;
43
33
  top: 50%;
44
34
  transform: translate(-50%, -50%);
45
- background-color: #fff;
46
- @media (prefers-color-scheme: dark) {
47
- background-color: #000;
48
- }
49
35
  /* box-shadow: 0px 0px 12px 3px rgba(0, 0, 0, 0.19); */
50
36
  border-radius: 4px;
51
37
  z-index: 999;
@@ -74,7 +60,7 @@ const Modal: React.FC<ModalProps> = (props) => {
74
60
  }
75
61
  }}
76
62
  >
77
- <Container role="dialog" className="shadow-lg rounded-md border border-gray-100 dark:border-gray-800" onMouseDown={(e) => e.stopPropagation()}>
63
+ <Container role="dialog" className="bg-white dark:bg-zinc-900 shadow-lg rounded-md border border-gray-100 dark:border-gray-800" onMouseDown={(e) => e.stopPropagation()}>
78
64
  <div className="absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
79
65
  <button
80
66
  type="button"
@@ -14,7 +14,7 @@ export const ResizeDialog: React.FC<SizeSettingProps> = (props) => {
14
14
  const { t } = useTranslation("translation", { keyPrefix: "main.tabpanel.settings.size_setting" });
15
15
 
16
16
  return (
17
- <>
17
+ <div className="text-zinc-400">
18
18
  {children}
19
19
  <div className="mt-4 w-60">
20
20
  <input
@@ -52,7 +52,7 @@ export const ResizeDialog: React.FC<SizeSettingProps> = (props) => {
52
52
  {`${t("height")}: ${height}`}
53
53
  </output>
54
54
  </div>
55
- </>
55
+ </div>
56
56
  );
57
57
  };
58
58
 
@@ -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-800 overflow-x-auto overflow-y-hidden" onMouseLeave={clearEditStatus}>
36
- <nav className="-mb-px flex h-8 border-gray-200 dark:border-gray-800" role="tablist" aria-label="Tabs">
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>
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>