@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.
- package/dist/App.d.ts +5 -2
- package/dist/assets/explainer.worker-8428eb12.js.map +1 -1
- package/dist/assets/transform.worker-5d54ff09.js.map +1 -0
- package/dist/assets/viewQuery.worker-ffefc111.js.map +1 -0
- package/dist/components/callout.d.ts +2 -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/components/toolbar/components.d.ts +4 -1
- package/dist/components/toolbar/index.d.ts +2 -0
- package/dist/components/toolbar/toolbar-item.d.ts +3 -0
- package/dist/components/tooltip.d.ts +2 -0
- package/dist/dataSource/dataSelection/config.d.ts +1 -0
- package/dist/dataSource/dataSelection/utils.d.ts +2 -0
- package/dist/datasets/tmp/test.json +1 -0
- package/dist/fields/components.d.ts +0 -1
- package/dist/fields/filterField/filterEditDialog.d.ts +1 -1
- package/dist/graphic-walker.es.js +23930 -23320
- package/dist/graphic-walker.es.js.map +1 -1
- package/dist/graphic-walker.umd.js +143 -273
- package/dist/graphic-walker.umd.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/interfaces.d.ts +23 -1
- package/dist/lib/execExp.d.ts +8 -0
- package/dist/lib/interfaces.d.ts +22 -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 +5 -0
- package/dist/models/visSpecHistory.d.ts +2 -0
- package/dist/renderer/index.d.ts +6 -3
- package/dist/renderer/specRenderer.d.ts +13 -0
- package/dist/services.d.ts +4 -1
- package/dist/store/commonStore.d.ts +6 -0
- package/dist/store/index.d.ts +3 -2
- package/dist/store/visualSpecStore.d.ts +11 -4
- package/dist/utils/dataPrep.d.ts +2 -0
- package/dist/utils/index.d.ts +3 -5
- package/dist/utils/media.d.ts +2 -1
- package/dist/utils/save.d.ts +1 -2
- package/dist/vis/react-vega.d.ts +4 -23
- package/dist/vis/spec/aggregate.d.ts +4 -0
- package/dist/vis/spec/encode.d.ts +19 -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/view.d.ts +67 -0
- package/dist/vis/theme.d.ts +36 -20
- package/dist/visualSettings/index.d.ts +2 -1
- package/dist/workers/transform.d.ts +2 -0
- package/package.json +4 -3
- package/src/App.tsx +23 -15
- package/src/components/callout.tsx +9 -7
- package/src/components/clickMenu.tsx +1 -7
- 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/modal.tsx +1 -15
- package/src/components/sizeSetting.tsx +2 -2
- package/src/components/tabs/defaultTab.tsx +4 -2
- package/src/components/tabs/editableTab.tsx +75 -40
- package/src/components/toolbar/components.tsx +8 -23
- package/src/components/toolbar/index.tsx +11 -4
- package/src/components/toolbar/toolbar-button.tsx +2 -1
- package/src/components/toolbar/toolbar-item.tsx +17 -12
- package/src/components/toolbar/toolbar-select-button.tsx +9 -13
- package/src/components/toolbar/toolbar-toggle-button.tsx +2 -1
- package/src/components/tooltip.tsx +10 -6
- package/src/dataSource/dataSelection/config.ts +11 -0
- package/src/dataSource/dataSelection/csvData.tsx +72 -40
- package/src/dataSource/dataSelection/gwFile.tsx +2 -2
- package/src/dataSource/dataSelection/utils.ts +28 -0
- package/src/dataSource/index.tsx +2 -3
- package/src/dataSource/utils.ts +8 -3
- package/src/fields/components.tsx +13 -50
- package/src/fields/datasetFields/index.tsx +3 -4
- package/src/fields/datasetFields/meaFields.tsx +12 -4
- package/src/fields/encodeFields/singleEncodeEditor.tsx +1 -1
- package/src/fields/filterField/filterEditDialog.tsx +63 -99
- package/src/fields/filterField/slider.tsx +1 -1
- package/src/index.css +4 -4
- package/src/index.tsx +22 -22
- package/src/insightBoard/mainBoard.tsx +9 -2
- package/src/interfaces.ts +30 -3
- package/src/lib/execExp.ts +147 -0
- package/src/lib/interfaces.ts +39 -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 +23 -0
- package/src/locales/en-US.json +8 -3
- package/src/locales/i18n.ts +7 -1
- package/src/locales/ja-JP.json +197 -0
- package/src/locales/zh-CN.json +8 -3
- package/src/main.tsx +1 -1
- package/src/models/visSpecHistory.ts +14 -0
- package/src/renderer/index.tsx +58 -101
- package/src/renderer/specRenderer.tsx +119 -0
- package/src/segments/segmentNav.tsx +3 -16
- package/src/segments/visNav.tsx +17 -6
- package/src/services.ts +37 -1
- package/src/store/commonStore.ts +14 -9
- package/src/store/index.tsx +11 -4
- package/src/store/visualSpecStore.ts +89 -50
- package/src/utils/dataPrep.ts +24 -0
- package/src/utils/index.ts +16 -17
- package/src/utils/media.ts +16 -11
- package/src/utils/normalization.ts +3 -1
- package/src/utils/save.ts +1 -2
- package/src/vis/react-vega.tsx +11 -332
- package/src/vis/spec/aggregate.ts +13 -0
- package/src/vis/spec/encode.ts +69 -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/view.ts +138 -0
- package/src/vis/theme.ts +35 -25
- package/src/visualSettings/index.tsx +22 -33
- 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/components/container.d.ts +0 -2
- package/dist/dataSource/pannel.d.ts +0 -5
- package/src/components/container.tsx +0 -25
- 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=
|
|
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(
|
|
53
|
-
<p className="mt-1 text-sm text-gray-500">{t(
|
|
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
|
|
77
|
-
<
|
|
78
|
-
className="
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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-
|
|
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
|
|
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 {
|
|
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
|
+
// }
|
package/src/dataSource/index.tsx
CHANGED
|
@@ -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
|
-
<
|
|
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
|
-
</
|
|
91
|
+
</div>
|
|
93
92
|
);
|
|
94
93
|
};
|
|
95
94
|
|
package/src/dataSource/utils.ts
CHANGED
|
@@ -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
|
|
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);
|