@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
@@ -39,21 +39,15 @@ export const useHandlers = (action: () => void, disabled: boolean, triggerKeys:
39
39
  }), [allowPropagation]);
40
40
  };
41
41
 
42
- export const ToolbarContainer = styled.div`
42
+ export const ToolbarContainer = styled.div<{ dark: boolean }>`
43
43
  --height: 36px;
44
44
  --icon-size: 18px;
45
45
  width: 100%;
46
46
  height: var(--height);
47
- background-color: var(--background-color);
48
- color: var(--color);
47
+ background-color: ${({ dark }) => dark ? 'var(--background-color-dark)' : 'var(--background-color)'};
48
+ color: ${({ dark }) => dark ? 'var(--color-dark)' : 'var(--color)'};
49
49
  border: 1px solid;
50
- border-color: #e5e7eb;
51
- // dark mode
52
- @media (prefers-color-scheme: dark) {
53
- background-color: var(--background-color-dark);
54
- color: var(--color-dark);
55
- border-color: #4b5563;
56
- }
50
+ border-color: ${({ dark }) => dark ? '#4b5563' : '#e5e7eb'};
57
51
  /* box-shadow: 0px 1px 3px 1px rgba(136, 136, 136, 0.1); */
58
52
  border-radius: 2px;
59
53
  overflow: hidden;
@@ -73,7 +67,7 @@ export const ToolbarSplitter = styled.div`
73
67
  background: #bbbbbb50;
74
68
  `;
75
69
 
76
- export const ToolbarItemContainerElement = styled.div<{ split: boolean }>`
70
+ export const ToolbarItemContainerElement = styled.div<{ split: boolean; dark: boolean }>`
77
71
  display: inline-flex;
78
72
  flex-direction: row;
79
73
  user-select: none;
@@ -81,7 +75,7 @@ export const ToolbarItemContainerElement = styled.div<{ split: boolean }>`
81
75
  width: ${({ split }) => split ? 'calc(var(--height) + 10px)' : 'var(--height)'};
82
76
  height: var(--height);
83
77
  overflow: hidden;
84
- color: var(--color);
78
+ color: ${({ dark }) => dark ? 'var(--dark-mode-color)' : 'var(--color)'};
85
79
  position: relative;
86
80
  > svg {
87
81
  flex-grow: 0;
@@ -102,8 +96,8 @@ export const ToolbarItemContainerElement = styled.div<{ split: boolean }>`
102
96
  &[aria-disabled=false] {
103
97
  cursor: pointer;
104
98
  :hover, :focus, &.open {
105
- --background-color: #FEFEFE;
106
- color: var(--color-hover);
99
+ --background-color: ${({ dark }) => dark ? '#202020' : '#FEFEFE'};
100
+ color: ${({ dark }) => dark ? 'var(--dark-mode-color-hover)' : 'var(--color-hover)'};
107
101
  &.split * svg {
108
102
  pointer-events: none;
109
103
  transform: translate(-50%, -20%);
@@ -114,14 +108,5 @@ export const ToolbarItemContainerElement = styled.div<{ split: boolean }>`
114
108
  background-color: var(--background-color);
115
109
  }
116
110
  }
117
- @media (prefers-color-scheme: dark) {
118
- color: var(--dark-mode-color);
119
- &[aria-disabled=false] {
120
- :hover, :focus, &.open {
121
- --background-color: #202020;
122
- color: var(--dark-mode-color-hover);
123
- }
124
- }
125
- }
126
111
  transition: color 100ms, background-image 100ms;
127
112
  `;
@@ -1,10 +1,12 @@
1
1
  import React, { CSSProperties, memo, ReactNode, useState } from "react";
2
2
  import styled from "styled-components";
3
+ import type { IDarkMode } from "../../interfaces";
4
+ import { useCurrentMediaTheme } from "../../utils/media";
3
5
  import { ToolbarContainer, ToolbarSplitter } from "./components";
4
6
  import ToolbarItem, { ToolbarItemProps, ToolbarItemSplitter } from "./toolbar-item";
5
7
 
6
8
 
7
- const Root = styled.div`
9
+ const Root = styled.div<{ darkModePreference: IDarkMode }>`
8
10
  width: 100%;
9
11
  --background-color: #f7f7f7;
10
12
  --color: #777;
@@ -16,9 +18,11 @@ const Root = styled.div`
16
18
  --dark-mode-color-hover: #ccc;
17
19
  --dark-mode-blue: #282958;
18
20
  --dark-mode-blue-dark: #1d1e38;
21
+ --dark-mode-preference: ${({ darkModePreference }) => darkModePreference};
19
22
  `;
20
23
 
21
24
  export interface ToolbarProps {
25
+ darkModePreference?: IDarkMode;
22
26
  items: ToolbarItemProps[];
23
27
  styles?: Partial<{
24
28
  root: CSSProperties & Record<string, string>;
@@ -29,13 +33,15 @@ export interface ToolbarProps {
29
33
  }>;
30
34
  }
31
35
 
32
- const Toolbar = memo<ToolbarProps>(function Toolbar ({ items, styles }) {
36
+ const Toolbar = memo<ToolbarProps>(function Toolbar ({ darkModePreference = 'media', items, styles }) {
33
37
  const [openedKey, setOpenedKey] = useState<string | null>(null);
34
38
  const [slot, setSlot] = useState<ReactNode>(null);
35
39
 
40
+ const dark = useCurrentMediaTheme(darkModePreference) === 'dark';
41
+
36
42
  return (
37
- <Root style={styles?.root}>
38
- <ToolbarContainer style={styles?.container}>
43
+ <Root darkModePreference={darkModePreference} style={styles?.root}>
44
+ <ToolbarContainer dark={dark} style={styles?.container}>
39
45
  {items.map((item, i) => {
40
46
  if (item === ToolbarItemSplitter) {
41
47
  return <ToolbarSplitter key={i} />;
@@ -48,6 +54,7 @@ const Toolbar = memo<ToolbarProps>(function Toolbar ({ items, styles }) {
48
54
  openedKey={openedKey}
49
55
  setOpenedKey={setOpenedKey}
50
56
  renderSlot={node => setSlot(node)}
57
+ darkModePreference={darkModePreference}
51
58
  />
52
59
  );
53
60
  })}
@@ -8,7 +8,7 @@ export interface ToolbarButtonItem extends IToolbarItem {
8
8
  }
9
9
 
10
10
  const ToolbarButton = memo<IToolbarProps<ToolbarButtonItem>>(function ToolbarButton(props) {
11
- const { item, styles } = props;
11
+ const { item, styles, darkModePreference } = props;
12
12
  const { icon: Icon, label, disabled, onClick } = item;
13
13
  const handlers = useHandlers(() => onClick?.(), disabled ?? false);
14
14
 
@@ -22,6 +22,7 @@ const ToolbarButton = memo<IToolbarProps<ToolbarButtonItem>>(function ToolbarBut
22
22
  <ToolbarItemContainer
23
23
  props={props}
24
24
  handlers={onClick ? handlers : null}
25
+ darkModePreference={darkModePreference}
25
26
  >
26
27
  <Icon style={mergedIconStyles} />
27
28
  </ToolbarItemContainer>
@@ -8,6 +8,7 @@ import { ToolbarContainer, ToolbarItemContainerElement, ToolbarSplitter, useHand
8
8
  import Toolbar, { ToolbarProps } from ".";
9
9
  import Tooltip from "../tooltip";
10
10
  import Callout from "../callout";
11
+ import { useCurrentMediaTheme } from "../../utils/media";
11
12
 
12
13
 
13
14
  const ToolbarSplit = styled.div<{ open: boolean }>`
@@ -34,10 +35,7 @@ const ToolbarSplit = styled.div<{ open: boolean }>`
34
35
  const FormContainer = styled(ToolbarContainer)`
35
36
  width: max-content;
36
37
  height: max-content;
37
- background-color: #fff;
38
- @media (prefers-color-scheme: dark) {
39
- background-color: #000;
40
- }
38
+ background-color: ${({ dark }) => dark ? '#000' : '#fff'};
41
39
  `;
42
40
 
43
41
  export interface IToolbarItem {
@@ -65,6 +63,7 @@ export type ToolbarItemProps = (
65
63
 
66
64
  export interface IToolbarProps<P extends Exclude<ToolbarItemProps, typeof ToolbarItemSplitter> = Exclude<ToolbarItemProps, typeof ToolbarItemSplitter>> {
67
65
  item: P;
66
+ darkModePreference: NonNullable<ToolbarProps['darkModePreference']>;
68
67
  styles?: ToolbarProps['styles'];
69
68
  openedKey: string | null;
70
69
  setOpenedKey: (key: string | null) => void;
@@ -74,6 +73,7 @@ export interface IToolbarProps<P extends Exclude<ToolbarItemProps, typeof Toolba
74
73
  let idFlag = 0;
75
74
 
76
75
  export const ToolbarItemContainer = memo<{
76
+ darkModePreference: NonNullable<ToolbarProps['darkModePreference']>;
77
77
  props: IToolbarProps;
78
78
  handlers: ReturnType<typeof useHandlers> | null;
79
79
  children: unknown;
@@ -84,6 +84,7 @@ export const ToolbarItemContainer = memo<{
84
84
  styles, openedKey, setOpenedKey, renderSlot,
85
85
  },
86
86
  handlers,
87
+ darkModePreference,
87
88
  children,
88
89
  ...props
89
90
  }
@@ -130,15 +131,18 @@ export const ToolbarItemContainer = memo<{
130
131
 
131
132
  useEffect(() => {
132
133
  if (opened && menu) {
133
- renderSlot(<Toolbar {...menu} />);
134
+ renderSlot(<Toolbar {...menu} darkModePreference={darkModePreference} />);
134
135
  return () => renderSlot(null);
135
136
  }
136
137
  }, [opened, menu, renderSlot]);
137
138
 
139
+ const dark = useCurrentMediaTheme(darkModePreference) === 'dark';
140
+
138
141
  return (
139
142
  <>
140
- <Tooltip content={label}>
143
+ <Tooltip content={label} darkModePreference={darkModePreference}>
141
144
  <ToolbarItemContainerElement
145
+ dark={dark}
142
146
  role="button" tabIndex={disabled ? undefined : 0} aria-label={label} aria-disabled={disabled ?? false}
143
147
  split={Boolean(form || menu)}
144
148
  style={styles?.item}
@@ -190,8 +194,8 @@ export const ToolbarItemContainer = memo<{
190
194
  </ToolbarItemContainerElement>
191
195
  </Tooltip>
192
196
  {opened && form && (
193
- <Callout target={`#${id}`}>
194
- <FormContainer onMouseDown={e => e.stopPropagation()}>
197
+ <Callout target={`#${id}`} darkModePreference={darkModePreference}>
198
+ <FormContainer dark={dark} onMouseDown={e => e.stopPropagation()}>
195
199
  {form}
196
200
  </FormContainer>
197
201
  </Callout>
@@ -202,20 +206,21 @@ export const ToolbarItemContainer = memo<{
202
206
 
203
207
  const ToolbarItem = memo<{
204
208
  item: ToolbarItemProps;
209
+ darkModePreference: NonNullable<ToolbarProps['darkModePreference']>;
205
210
  styles?: ToolbarProps['styles'];
206
211
  openedKey: string | null;
207
212
  setOpenedKey: (key: string | null) => void;
208
213
  renderSlot: (node: ReactNode) => void;
209
- }>(function ToolbarItem ({ item, styles, openedKey, setOpenedKey, renderSlot }) {
214
+ }>(function ToolbarItem ({ item, styles, openedKey, setOpenedKey, renderSlot, darkModePreference }) {
210
215
  if (item === ToolbarItemSplitter) {
211
216
  return <ToolbarSplitter />;
212
217
  }
213
218
  if ('checked' in item) {
214
- return <ToolbarToggleButton item={item} styles={styles} openedKey={openedKey} setOpenedKey={setOpenedKey} renderSlot={renderSlot} />;
219
+ return <ToolbarToggleButton item={item} styles={styles} openedKey={openedKey} setOpenedKey={setOpenedKey} renderSlot={renderSlot} darkModePreference={darkModePreference} />;
215
220
  } else if ('options' in item) {
216
- return <ToolbarSelectButton item={item} styles={styles} openedKey={openedKey} setOpenedKey={setOpenedKey} renderSlot={renderSlot} />;
221
+ return <ToolbarSelectButton item={item} styles={styles} openedKey={openedKey} setOpenedKey={setOpenedKey} renderSlot={renderSlot} darkModePreference={darkModePreference} />;
217
222
  }
218
- return <ToolbarButton item={item} styles={styles} openedKey={openedKey} setOpenedKey={setOpenedKey} renderSlot={renderSlot} />;
223
+ return <ToolbarButton item={item} styles={styles} openedKey={openedKey} setOpenedKey={setOpenedKey} renderSlot={renderSlot} darkModePreference={darkModePreference} />;
219
224
  });
220
225
 
221
226
 
@@ -4,6 +4,7 @@ import produce from "immer";
4
4
  import { IToolbarItem, IToolbarProps, ToolbarItemContainer } from "./toolbar-item";
5
5
  import { ToolbarContainer, useHandlers, ToolbarItemContainerElement } from "./components";
6
6
  import Callout from "../callout";
7
+ import { useCurrentMediaTheme } from "../../utils/media";
7
8
 
8
9
 
9
10
  const OptionGroup = styled(ToolbarContainer)`
@@ -21,16 +22,7 @@ const OptionGroup = styled(ToolbarContainer)`
21
22
  --dark-mode-color: #aaa;
22
23
  --dark-mode-color-hover: #ccc;
23
24
  --dark-mode-blue: #282958;
24
- background-color: var(--background-color);
25
- // dark mode
26
- @media (prefers-color-scheme: dark) {
27
- /* --dark-mode-background-color: #1f1f1f;
28
- --dark-mode-background-color-hover: #2f2f2f;
29
- --dark-mode-color: #aaa;
30
- --dark-mode-color-hover: #ccc;
31
- --dark-mode-blue: #282958; */
32
- background-color: var(--dark-mode-background-color);
33
- }
25
+ background-color: ${({ dark }) => dark ? 'var(--dark-mode-background-color)' : 'var(--background-color)'};
34
26
  `;
35
27
 
36
28
  const Option = styled(ToolbarItemContainerElement)`
@@ -85,7 +77,7 @@ export interface ToolbarSelectButtonItem<T extends string = string> extends IToo
85
77
  }
86
78
 
87
79
  const ToolbarSelectButton = memo<IToolbarProps<ToolbarSelectButtonItem>>(function ToolbarSelectButton(props) {
88
- const { item, styles, openedKey, setOpenedKey } = props;
80
+ const { darkModePreference, item, styles, openedKey, setOpenedKey } = props;
89
81
  const { key, icon: Icon, disabled, options, value, onSelect } = item;
90
82
  const id = `${key}::button`;
91
83
 
@@ -134,9 +126,12 @@ const ToolbarSelectButton = memo<IToolbarProps<ToolbarSelectButtonItem>>(functio
134
126
  ...item.styles?.icon,
135
127
  };
136
128
 
129
+ const dark = useCurrentMediaTheme(darkModePreference) === 'dark';
130
+
137
131
  return (
138
132
  <>
139
133
  <ToolbarItemContainer
134
+ darkModePreference={darkModePreference}
140
135
  props={produce(props, draft => {
141
136
  if (currentOption) {
142
137
  draft.item.label = `${draft.item.label}: ${currentOption.label}`;
@@ -163,8 +158,8 @@ const ToolbarSelectButton = memo<IToolbarProps<ToolbarSelectButtonItem>>(functio
163
158
  <TriggerFlag aria-hidden id={id} />
164
159
  </ToolbarItemContainer>
165
160
  {opened && (
166
- <Callout target={`#${id}`}>
167
- <OptionGroup role="listbox" aria-activedescendant={`${id}::${value}`} aria-describedby={id} aria-disabled={disabled} onMouseDown={e => e.stopPropagation()}>
161
+ <Callout target={`#${id}`} darkModePreference={darkModePreference}>
162
+ <OptionGroup dark={dark} role="listbox" aria-activedescendant={`${id}::${value}`} aria-describedby={id} aria-disabled={disabled} onMouseDown={e => e.stopPropagation()}>
168
163
  {options.map((option, idx, arr) => {
169
164
  const selected = option.key === value;
170
165
  const OptionIcon = option.icon;
@@ -173,6 +168,7 @@ const ToolbarSelectButton = memo<IToolbarProps<ToolbarSelectButtonItem>>(functio
173
168
  const next = arr[(idx + 1) % arr.length];
174
169
  return (
175
170
  <Option
171
+ dark={dark}
176
172
  key={option.key}
177
173
  id={optionId}
178
174
  role="option"
@@ -46,7 +46,7 @@ export interface ToolbarToggleButtonItem extends IToolbarItem {
46
46
  }
47
47
 
48
48
  const ToolbarToggleButton = memo<IToolbarProps<ToolbarToggleButtonItem>>(function ToolbarToggleButton(props) {
49
- const { item, styles } = props;
49
+ const { item, styles, darkModePreference } = props;
50
50
  const { icon: Icon, label, disabled, checked, onChange } = item;
51
51
  const handlers = useHandlers(() => onChange(!checked), disabled ?? false);
52
52
 
@@ -62,6 +62,7 @@ const ToolbarToggleButton = memo<IToolbarProps<ToolbarToggleButtonItem>>(functio
62
62
  handlers={handlers}
63
63
  role="checkbox"
64
64
  aria-checked={checked}
65
+ darkModePreference={darkModePreference}
65
66
  >
66
67
  <ToggleContainer checked={checked}>
67
68
  <Icon style={mergedIconStyles} />
@@ -1,7 +1,9 @@
1
1
  import React, { memo, useContext, useEffect, useMemo, useRef, 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 TooltipProps {
7
9
  children: JSX.Element;
@@ -12,12 +14,13 @@ export interface TooltipProps {
12
14
  hideDelay?: number;
13
15
  /** @default 3_000 */
14
16
  autoHide?: number;
17
+ darkModePreference: IDarkMode;
15
18
  }
16
19
 
17
20
  const attrName = "data-tooltip-host-id";
18
21
  let flag = 0;
19
22
 
20
- const Bubble = styled.div`
23
+ const Bubble = styled.div<{ dark: boolean }>`
21
24
  border-radius: 1px;
22
25
  transform: translate(-50%, -100%);
23
26
  filter: drop-shadow(0 1.6px 1.2px rgba(0, 0, 0, 0.15)) drop-shadow(0 -1px 1px rgba(0, 0, 0, 0.12));
@@ -31,10 +34,7 @@ const Bubble = styled.div`
31
34
  width: 8px;
32
35
  height: 8px;
33
36
  transform: translate(-50%, 50%) rotate(45deg);
34
- background-color: #fff;
35
- @media (prefers-color-scheme: dark) {
36
- background-color: #000;
37
- }
37
+ background-color: ${({ dark }) => dark ? '#000' : '#fff'};
38
38
  border-radius: 1px;
39
39
  }
40
40
  `;
@@ -45,6 +45,7 @@ const Tooltip = memo<TooltipProps>(function Tooltip({
45
45
  autoHide = 3_000,
46
46
  showDelay = 250,
47
47
  hideDelay = 250,
48
+ darkModePreference = 'media',
48
49
  }) {
49
50
  const hostId = useMemo(() => flag++, []);
50
51
  const [pos, setPos] = useState<[number, number]>([0, 0]);
@@ -121,6 +122,8 @@ const Tooltip = memo<TooltipProps>(function Tooltip({
121
122
  }
122
123
  }, [root, hostId]);
123
124
 
125
+ const darkMode = useCurrentMediaTheme(darkModePreference);
126
+
124
127
  return (
125
128
  <>
126
129
  {element}
@@ -128,7 +131,8 @@ const Tooltip = memo<TooltipProps>(function Tooltip({
128
131
  root &&
129
132
  createPortal(
130
133
  <Bubble
131
- className="fixed text-xs p-1 px-3 text-gray-500 bg-white dark:bg-zinc-900 z-50"
134
+ className={`${darkMode === 'dark' ? 'dark bg-zinc-900' : 'bg-white'} fixed text-xs p-1 px-3 text-gray-500 z-50`}
135
+ dark={darkMode === 'dark'}
132
136
  onMouseOver={() => setHover(true)}
133
137
  onMouseOut={() => setHover(false)}
134
138
  style={{ left: pos[0], top: pos[1] - 4 }}
@@ -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
  ]
@@ -9,7 +9,10 @@ import { useTranslation } from "react-i18next";
9
9
  import DefaultButton from "../../components/button/default";
10
10
  import PrimaryButton from "../../components/button/primary";
11
11
  import DropdownSelect from "../../components/dropdownSelect";
12
- import { charsetOptions } from "./config";
12
+ import { SUPPORTED_FILE_TYPES, charsetOptions } from "./config";
13
+ import { classNames } from "../../utils";
14
+ import { RadioGroup } from "@headlessui/react";
15
+ import { jsonReader } from "./utils";
13
16
 
14
17
  const Container = styled.div`
15
18
  overflow-x: auto;
@@ -22,6 +25,7 @@ const CSVData: React.FC<ICSVData> = (props) => {
22
25
  const { commonStore } = useGlobalStore();
23
26
  const { tmpDSName, tmpDataSource, tmpDSRawFields } = commonStore;
24
27
  const [encoding, setEncoding] = useState<string>("utf-8");
28
+ const [fileType, setFileType] = useState<string>("csv");
25
29
 
26
30
  const onSubmitData = useCallback(() => {
27
31
  commonStore.commitTempDS();
@@ -30,6 +34,27 @@ const CSVData: React.FC<ICSVData> = (props) => {
30
34
  const { t } = useTranslation("translation", { keyPrefix: "DataSource.dialog.file" });
31
35
  const fileLoaded = tmpDataSource.length > 0 && tmpDSRawFields.length > 0;
32
36
 
37
+ const fileUpload = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
38
+ const files = e.target.files;
39
+ if (files !== null) {
40
+ const file = files[0];
41
+ if (fileType === 'csv') {
42
+ FileReader.csvReader({
43
+ file,
44
+ config: { type: "reservoirSampling", size: Infinity },
45
+ onLoading: () => {},
46
+ encoding,
47
+ }).then((data) => {
48
+ commonStore.updateTempDS(data as IRow[]);
49
+ });
50
+ } else {
51
+ jsonReader(file).then((data) => {
52
+ commonStore.updateTempDS(data as IRow[]);
53
+ });
54
+ }
55
+ }
56
+ }, [fileType, encoding]);
57
+
33
58
  return (
34
59
  <Container>
35
60
  {!fileLoaded && (
@@ -49,49 +74,56 @@ const CSVData: React.FC<ICSVData> = (props) => {
49
74
  d="M9 13h6m-3-3v6m-9 1V7a2 2 0 012-2h6l2 2h6a2 2 0 012 2v8a2 2 0 01-2 2H5a2 2 0 01-2-2z"
50
75
  />
51
76
  </svg>
52
- <h3 className="mt-2 text-sm font-semibold text-gray-900">{t('choose_file')}</h3>
53
- <p className="mt-1 text-sm text-gray-500">{t('get_start_desc')}</p>
77
+ <h3 className="mt-2 text-sm font-semibold text-gray-900 dark:text-gray-50">{t("choose_file")}</h3>
78
+ <p className="mt-1 text-sm text-gray-500">{t("get_start_desc")}</p>
54
79
  </div>
55
80
  )}
56
- <input
57
- style={{ display: "none" }}
58
- type="file"
59
- ref={fileRef}
60
- onChange={(e) => {
61
- const files = e.target.files;
62
- if (files !== null) {
63
- const file = files[0];
64
- FileReader.csvReader({
65
- file,
66
- config: { type: "reservoirSampling", size: Infinity },
67
- onLoading: () => {},
68
- encoding,
69
- }).then((data) => {
70
- commonStore.updateTempDS(data as IRow[]);
71
- });
72
- }
73
- }}
74
- />
81
+ <input style={{ display: "none" }} type="file" ref={fileRef} onChange={fileUpload} />
75
82
  {!fileLoaded && (
76
- <div className="my-1 flex justify-center">
77
- <DefaultButton
78
- className="mr-2"
79
- onClick={() => {
80
- if (fileRef.current) {
81
- fileRef.current.click();
82
- }
83
- }}
84
- text={t("open")}
85
- />
86
- <div className="inline-block relative">
87
- <DropdownSelect
88
- buttonClassName="w-36"
89
- options={charsetOptions}
90
- selectedKey={encoding}
91
- onSelect={(k) => {
92
- setEncoding(k);
83
+ <div className="my-1">
84
+ <div className="flex justify-center">
85
+ <RadioGroup value={fileType} onChange={setFileType} className="mt-2">
86
+ <RadioGroup.Label className="sr-only"> Choose a memory option </RadioGroup.Label>
87
+ <div className="grid grid-cols-2 gap-3">
88
+ {SUPPORTED_FILE_TYPES.map((option) => (
89
+ <RadioGroup.Option
90
+ key={option.value}
91
+ value={option.value}
92
+ className={({ active, checked }) =>
93
+ classNames(
94
+ checked
95
+ ? "bg-indigo-600 text-white hover:bg-indigo-500"
96
+ : "ring-1 ring-inset ring-gray-300 hover:bg-gray-50 dark:hover:bg-gray-800",
97
+ "flex cursor-pointer items-center justify-center rounded py-1 px-8 text-sm font-semibold uppercase sm:flex-1"
98
+ )
99
+ }
100
+ >
101
+ <RadioGroup.Label as="span">{option.label}</RadioGroup.Label>
102
+ </RadioGroup.Option>
103
+ ))}
104
+ </div>
105
+ </RadioGroup>
106
+ </div>
107
+ <div className="my-1 flex justify-center">
108
+ <DefaultButton
109
+ className="mr-2"
110
+ onClick={() => {
111
+ if (fileRef.current) {
112
+ fileRef.current.click();
113
+ }
93
114
  }}
115
+ text={t("open")}
94
116
  />
117
+ <div className="inline-block relative">
118
+ <DropdownSelect
119
+ buttonClassName="w-36"
120
+ options={charsetOptions}
121
+ selectedKey={encoding}
122
+ onSelect={(k) => {
123
+ setEncoding(k);
124
+ }}
125
+ />
126
+ </div>
95
127
  </div>
96
128
  </div>
97
129
  )}
@@ -107,7 +139,7 @@ const CSVData: React.FC<ICSVData> = (props) => {
107
139
  onChange={(e) => {
108
140
  commonStore.updateTempName(e.target.value);
109
141
  }}
110
- className="text-xs mr-2 p-2 rounded border border-gray-200 dark:border-gray-800 outline-none focus:outline-none focus:border-blue-500 placeholder:italic placeholder:text-slate-400 dark:bg-stone-900"
142
+ className="text-xs mr-2 p-2 rounded border border-gray-200 dark:border-gray-700 outline-none focus:outline-none focus:border-blue-500 placeholder:italic placeholder:text-slate-400 dark:bg-stone-900"
111
143
  />
112
144
  <PrimaryButton
113
145
  className="mr-2"
@@ -1,4 +1,4 @@
1
- import React, { useRef, useCallback } from "react";
1
+ import React from "react";
2
2
  import { useGlobalStore } from "../../store";
3
3
  import { observer } from "mobx-react-lite";
4
4
 
@@ -7,7 +7,7 @@ interface GWFileProps {
7
7
  fileRef: React.RefObject<HTMLInputElement>;
8
8
  }
9
9
  const GWFile: React.FC<GWFileProps> = (props) => {
10
- const { commonStore, vizStore } = useGlobalStore();
10
+ const { vizStore } = useGlobalStore();
11
11
 
12
12
  return (
13
13
  <input
@@ -0,0 +1,28 @@
1
+ import { IDataSetInfo, IRow } from "../../interfaces";
2
+
3
+ export function jsonReader (file: File): Promise<IRow[]> {
4
+ return new Promise((resolve, reject) => {
5
+ const reader = new FileReader();
6
+ reader.onload = () => {
7
+ try {
8
+ const data = JSON.parse(reader.result as string);
9
+ if (!Array.isArray(data)) {
10
+ throw new Error('Invalid JSON file');
11
+ }
12
+ resolve(data);
13
+ } catch (e) {
14
+ reject(e);
15
+ }
16
+ };
17
+ reader.readAsText(file);
18
+ });
19
+ }
20
+
21
+ // export function jsonArray2DatasetInfo (data: IRow[]): IDataSetInfo {
22
+ // const fields = Object.keys(data[0]);
23
+ // return {
24
+ // name: 'New Dataset',
25
+ // rawFields: fields.map(f => ({ name: f, type: 'number' })),
26
+ // dataSource: data
27
+ // }
28
+ // }
@@ -2,7 +2,6 @@ import React, { useRef } from "react";
2
2
  import { observer } from "mobx-react-lite";
3
3
  import { CheckCircleIcon, ArrowPathIcon } from "@heroicons/react/24/outline";
4
4
  import { useTranslation } from "react-i18next";
5
- import { Container } from "../components/container";
6
5
  import Modal from "../components/modal";
7
6
  import { useGlobalStore } from "../store";
8
7
  import { download } from "../utils/save";
@@ -25,7 +24,7 @@ const DataSourceSegment: React.FC<DSSegmentProps> = (props) => {
25
24
  const { currentDataset, datasets, showDSPanel } = commonStore;
26
25
 
27
26
  return (
28
- <Container className="flex items-center">
27
+ <div className="flex items-center m-4 p-4 border border-gray-200 dark:border-gray-700">
29
28
  <GwFile fileRef={gwFileRef} />
30
29
  {!preWorkDone && (
31
30
  <div className="animate-spin inline-block mr-2 ml-2 w-4 h-4 rounded-full border-t-2 border-l-2 border-blue-500"></div>
@@ -89,7 +88,7 @@ const DataSourceSegment: React.FC<DSSegmentProps> = (props) => {
89
88
  )} */}
90
89
  {preWorkDone && <CheckCircleIcon className="text-green-500 w-5 inline-block ml-2" />}
91
90
  {!preWorkDone && <ArrowPathIcon className="text-yellow-500 w-5 inline-block ml-2" />}
92
- </Container>
91
+ </div>
93
92
  );
94
93
  };
95
94
 
@@ -2,6 +2,7 @@ import { IRow, IMutField } from "../interfaces";
2
2
  import { inferMeta } from "../lib/inferMeta";
3
3
  import { guardDataKeys } from "../utils/dataPrep";
4
4
 
5
+
5
6
  export function transData(dataSource: IRow[]): {
6
7
  dataSource: IRow[];
7
8
  fields: IMutField[];
@@ -12,7 +13,11 @@ export function transData(dataSource: IRow[]): {
12
13
  fields: [],
13
14
  };
14
15
  }
15
- const keys = Object.keys(dataSource[0]);
16
+ const sampleRecord = dataSource[0];
17
+ // const rawKeys = Object.keys(sampleRecord);
18
+ // let flatColKeys: string[] = flatNestKeys(sampleRecord);
19
+
20
+ const keys = Object.keys(sampleRecord);
16
21
  const metas = inferMeta({
17
22
  dataSource,
18
23
  fields: keys.map((k) => ({
@@ -21,7 +26,7 @@ export function transData(dataSource: IRow[]): {
21
26
  analyticType: "?",
22
27
  semanticType: "?",
23
28
  })),
24
- });
29
+ })
25
30
  const { safeData, safeMetas } = guardDataKeys(dataSource, metas);
26
31
  const finalData: IRow[] = [];
27
32
  for (let record of safeData) {
@@ -30,7 +35,7 @@ export function transData(dataSource: IRow[]): {
30
35
  if (field.semanticType === "quantitative") {
31
36
  newRecord[field.fid] = Number(record[field.fid]);
32
37
  } else {
33
- newRecord[field.fid] = record[field.fid];
38
+ newRecord[field.fid] = record[field.fid];//getValueByKeyPath(record, field.fid);// record[field.fid];
34
39
  }
35
40
  }
36
41
  finalData.push(newRecord);