@kanaries/graphic-walker 0.2.14 → 0.2.15
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 +3 -2
- package/dist/components/callout.d.ts +2 -0
- 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/fields/components.d.ts +0 -1
- package/dist/fields/filterField/filterEditDialog.d.ts +1 -1
- package/dist/graphic-walker.es.js +15103 -14997
- package/dist/graphic-walker.es.js.map +1 -1
- package/dist/graphic-walker.umd.js +134 -264
- package/dist/graphic-walker.umd.js.map +1 -1
- package/dist/interfaces.d.ts +2 -0
- package/dist/renderer/index.d.ts +6 -4
- package/dist/utils/media.d.ts +2 -1
- package/dist/vis/react-vega.d.ts +4 -2
- package/dist/vis/theme.d.ts +36 -20
- package/dist/visualSettings/index.d.ts +2 -1
- package/package.json +1 -1
- package/src/App.tsx +19 -14
- package/src/components/callout.tsx +9 -7
- package/src/components/clickMenu.tsx +1 -7
- package/src/components/modal.tsx +1 -15
- package/src/components/sizeSetting.tsx +2 -2
- package/src/components/tabs/editableTab.tsx +2 -2
- 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/csvData.tsx +1 -1
- package/src/dataSource/index.tsx +2 -3
- package/src/fields/components.tsx +13 -50
- package/src/fields/datasetFields/index.tsx +3 -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/insightBoard/mainBoard.tsx +9 -2
- package/src/interfaces.ts +4 -1
- package/src/locales/en-US.json +5 -2
- package/src/locales/i18n.ts +7 -0
- package/src/locales/ja-JP.json +195 -0
- package/src/locales/zh-CN.json +5 -2
- package/src/renderer/index.tsx +96 -71
- package/src/utils/media.ts +16 -11
- package/src/vis/react-vega.tsx +18 -3
- package/src/vis/theme.ts +23 -25
- package/src/visualSettings/index.tsx +12 -32
- package/dist/components/container.d.ts +0 -2
- package/src/components/container.tsx +0 -25
|
@@ -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 }}
|
|
@@ -107,7 +107,7 @@ const CSVData: React.FC<ICSVData> = (props) => {
|
|
|
107
107
|
onChange={(e) => {
|
|
108
108
|
commonStore.updateTempName(e.target.value);
|
|
109
109
|
}}
|
|
110
|
-
className="text-xs mr-2 p-2 rounded border border-gray-200 dark:border-gray-
|
|
110
|
+
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
111
|
/>
|
|
112
112
|
<PrimaryButton
|
|
113
113
|
className="mr-2"
|
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
|
|
|
@@ -3,38 +3,13 @@ import styled from "styled-components";
|
|
|
3
3
|
import { useTranslation } from 'react-i18next';
|
|
4
4
|
import { COLORS } from "../config";
|
|
5
5
|
|
|
6
|
-
export const AestheticSegment = styled.div`
|
|
7
|
-
border: 1px solid #e5e7eb;
|
|
8
|
-
// dark mode
|
|
9
|
-
@media (prefers-color-scheme: dark) {
|
|
10
|
-
border: 1px solid #2d3748;
|
|
11
|
-
}
|
|
12
|
-
font-size: 12px;
|
|
13
|
-
margin: 0.2em;
|
|
14
|
-
|
|
15
|
-
.aes-header{
|
|
16
|
-
border-bottom: 1px solid #e5e7eb;
|
|
17
|
-
@media (prefers-color-scheme: dark) {
|
|
18
|
-
border-bottom: 1px solid #2d3748;
|
|
19
|
-
}
|
|
20
|
-
padding: 0.6em;
|
|
21
|
-
h4 {
|
|
22
|
-
font-weight: 400;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
.aes-container{
|
|
26
|
-
/* overflow-x: auto; */
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
`
|
|
30
|
-
|
|
31
6
|
export const FieldListContainer: React.FC<{ name: string }> = (props) => {
|
|
32
7
|
const { t } = useTranslation('translation', { keyPrefix: 'constant.draggable_key' });
|
|
33
8
|
|
|
34
9
|
return (
|
|
35
|
-
<FieldListSegment>
|
|
36
|
-
<div className="fl-header">
|
|
37
|
-
<h4>{t(props.name)}</h4>
|
|
10
|
+
<FieldListSegment className="m-0.5 border border-gray-200 dark:border-gray-700">
|
|
11
|
+
<div className="fl-header border-r border-gray-200 dark:border-gray-800 cursor-default select-none">
|
|
12
|
+
<h4 className="font-normal">{t(props.name)}</h4>
|
|
38
13
|
</div>
|
|
39
14
|
<div className="fl-container">{props.children}</div>
|
|
40
15
|
</FieldListSegment>
|
|
@@ -45,12 +20,12 @@ export const AestheticFieldContainer: React.FC<{ name: string }> = props => {
|
|
|
45
20
|
const { t } = useTranslation('translation', { keyPrefix: 'constant.draggable_key' });
|
|
46
21
|
|
|
47
22
|
return (
|
|
48
|
-
<
|
|
49
|
-
<div className="
|
|
50
|
-
<h4>{t(props.name)}</h4>
|
|
23
|
+
<div className="m-0.5 text-xs border border-gray-200 dark:border-gray-700">
|
|
24
|
+
<div className="border-b border-gray-200 dark:border-gray-800 p-2 cursor-default select-none">
|
|
25
|
+
<h4 className="font-normal">{t(props.name)}</h4>
|
|
51
26
|
</div>
|
|
52
|
-
<div
|
|
53
|
-
</
|
|
27
|
+
<div>{props.children}</div>
|
|
28
|
+
</div>
|
|
54
29
|
);
|
|
55
30
|
}
|
|
56
31
|
|
|
@@ -58,12 +33,12 @@ export const FilterFieldContainer: React.FC = props => {
|
|
|
58
33
|
const { t } = useTranslation('translation', { keyPrefix: 'constant.draggable_key' });
|
|
59
34
|
|
|
60
35
|
return (
|
|
61
|
-
<
|
|
62
|
-
<div className="
|
|
63
|
-
<h4>{t('filters')}</h4>
|
|
36
|
+
<div className="m-0.5 text-xs border border-gray-200 dark:border-gray-700">
|
|
37
|
+
<div className="border-b border-gray-200 dark:border-gray-800 p-2 cursor-default select-none">
|
|
38
|
+
<h4 className="font-normal">{t('filters')}</h4>
|
|
64
39
|
</div>
|
|
65
|
-
<div
|
|
66
|
-
</
|
|
40
|
+
<div>{props.children}</div>
|
|
41
|
+
</div>
|
|
67
42
|
);
|
|
68
43
|
}
|
|
69
44
|
|
|
@@ -91,19 +66,11 @@ export const FilterFieldsContainer = styled.div({
|
|
|
91
66
|
|
|
92
67
|
export const FieldListSegment = styled.div`
|
|
93
68
|
display: flex;
|
|
94
|
-
border: 1px solid #e5e7eb;
|
|
95
|
-
@media (prefers-color-scheme: dark) {
|
|
96
|
-
border: 1px solid #2d3748;
|
|
97
|
-
}
|
|
98
69
|
margin: 0.2em;
|
|
99
70
|
font-size: 12px;
|
|
100
71
|
div.fl-header {
|
|
101
72
|
/* flex-basis: 100px; */
|
|
102
73
|
width: 100px;
|
|
103
|
-
border-right: 1px solid #e5e7eb;
|
|
104
|
-
@media (prefers-color-scheme: dark) {
|
|
105
|
-
border-right: 1px solid #2d3748;
|
|
106
|
-
}
|
|
107
74
|
flex-shrink: 0;
|
|
108
75
|
h4 {
|
|
109
76
|
margin: 0.6em;
|
|
@@ -112,10 +79,6 @@ export const FieldListSegment = styled.div`
|
|
|
112
79
|
}
|
|
113
80
|
div.fl-container {
|
|
114
81
|
flex-grow: 10;
|
|
115
|
-
/* display: flex;
|
|
116
|
-
flex-wrap: wrap; */
|
|
117
|
-
/* overflow-x: auto;
|
|
118
|
-
overflow-y: hidden; */
|
|
119
82
|
}
|
|
120
83
|
`;
|
|
121
84
|
|
|
@@ -2,11 +2,10 @@ import React from "react";
|
|
|
2
2
|
import { Droppable } from "@kanaries/react-beautiful-dnd";
|
|
3
3
|
import { useTranslation } from "react-i18next";
|
|
4
4
|
import styled from 'styled-components';
|
|
5
|
-
import { NestContainer } from "../../components/container";
|
|
6
5
|
import DimFields from "./dimFields";
|
|
7
6
|
import MeaFields from "./meaFields";
|
|
8
7
|
|
|
9
|
-
const DSContainer = styled
|
|
8
|
+
const DSContainer = styled.div`
|
|
10
9
|
@media (min-width: 768px) {
|
|
11
10
|
height: 680px;
|
|
12
11
|
}
|
|
@@ -16,14 +15,14 @@ const DatasetFields: React.FC = (props) => {
|
|
|
16
15
|
const { t } = useTranslation("translation", { keyPrefix: "main.tabpanel.DatasetFields" });
|
|
17
16
|
|
|
18
17
|
return (
|
|
19
|
-
<DSContainer className="border-gray-200 dark:border-gray-700 flex md:flex-col" style={{ paddingBlock: 0, paddingInline: '0.6em' }}>
|
|
18
|
+
<DSContainer className="p-1 m-0.5 border border-gray-200 dark:border-gray-700 flex md:flex-col" style={{ paddingBlock: 0, paddingInline: '0.6em' }}>
|
|
20
19
|
<h4 className="text-xs mb-2 flex-grow-0 cursor-default select-none mt-2">{t("field_list")}</h4>
|
|
21
20
|
<div className="pd-1 overflow-y-auto" style={{ maxHeight: "380px", minHeight: '100px' }}>
|
|
22
21
|
<Droppable droppableId="dimensions" direction="vertical">
|
|
23
22
|
{(provided, snapshot) => <DimFields provided={provided} />}
|
|
24
23
|
</Droppable>
|
|
25
24
|
</div>
|
|
26
|
-
<div className="border-t dark:border-gray-
|
|
25
|
+
<div className="border-t dark:border-gray-700 flex-grow pd-1 overflow-y-auto">
|
|
27
26
|
<Droppable droppableId="measures" direction="vertical">
|
|
28
27
|
{(provided, snapshot) => <MeaFields provided={provided} />}
|
|
29
28
|
</Droppable>
|
|
@@ -48,7 +48,7 @@ const SingleEncodeEditor: React.FC<SingleEncodeEditorProps> = (props) => {
|
|
|
48
48
|
>
|
|
49
49
|
<TrashIcon className="w-4" />
|
|
50
50
|
</div>
|
|
51
|
-
<div className="flex-1 flex items-center border border-gray-200 dark:border-gray-700 border-l-0 px-2 space-x-2">
|
|
51
|
+
<div className="flex-1 flex items-center border border-gray-200 dark:border-gray-700 border-l-0 px-2 space-x-2 truncate">
|
|
52
52
|
<span className="flex-1 truncate">
|
|
53
53
|
{channelItem.name}
|
|
54
54
|
</span>
|
|
@@ -1,64 +1,29 @@
|
|
|
1
|
-
import { CheckCircleIcon } from
|
|
2
|
-
import { observer } from
|
|
3
|
-
import React from
|
|
4
|
-
import { useTranslation } from
|
|
5
|
-
|
|
6
|
-
import Modal from
|
|
7
|
-
import type { IFilterField, IFilterRule } from
|
|
8
|
-
import { useGlobalStore } from
|
|
9
|
-
import Tabs, { RuleFormProps } from
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
onChange,
|
|
15
|
-
}) => {
|
|
16
|
-
return (
|
|
17
|
-
<Tabs
|
|
18
|
-
field={field}
|
|
19
|
-
onChange={onChange}
|
|
20
|
-
tabs={['range', 'one of']}
|
|
21
|
-
/>
|
|
22
|
-
);
|
|
1
|
+
import { CheckCircleIcon } from "@heroicons/react/24/outline";
|
|
2
|
+
import { observer } from "mobx-react-lite";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { useTranslation } from "react-i18next";
|
|
5
|
+
|
|
6
|
+
import Modal from "../../components/modal";
|
|
7
|
+
import type { IFilterField, IFilterRule } from "../../interfaces";
|
|
8
|
+
import { useGlobalStore } from "../../store";
|
|
9
|
+
import Tabs, { RuleFormProps } from "./tabs";
|
|
10
|
+
import DefaultButton from "../../components/button/default";
|
|
11
|
+
import PrimaryButton from "../../components/button/primary";
|
|
12
|
+
|
|
13
|
+
const QuantitativeRuleForm: React.FC<RuleFormProps> = ({ field, onChange }) => {
|
|
14
|
+
return <Tabs field={field} onChange={onChange} tabs={["range", "one of"]} />;
|
|
23
15
|
};
|
|
24
16
|
|
|
25
|
-
const NominalRuleForm: React.FC<RuleFormProps> = ({
|
|
26
|
-
field
|
|
27
|
-
onChange,
|
|
28
|
-
}) => {
|
|
29
|
-
return (
|
|
30
|
-
<Tabs
|
|
31
|
-
field={field}
|
|
32
|
-
onChange={onChange}
|
|
33
|
-
tabs={['one of']}
|
|
34
|
-
/>
|
|
35
|
-
);
|
|
17
|
+
const NominalRuleForm: React.FC<RuleFormProps> = ({ field, onChange }) => {
|
|
18
|
+
return <Tabs field={field} onChange={onChange} tabs={["one of"]} />;
|
|
36
19
|
};
|
|
37
20
|
|
|
38
|
-
const OrdinalRuleForm: React.FC<RuleFormProps> = ({
|
|
39
|
-
field,
|
|
40
|
-
onChange,
|
|
41
|
-
}) => {
|
|
42
|
-
return (
|
|
43
|
-
<Tabs
|
|
44
|
-
field={field}
|
|
45
|
-
onChange={onChange}
|
|
46
|
-
tabs={['range', 'one of']}
|
|
47
|
-
/>
|
|
48
|
-
);
|
|
21
|
+
const OrdinalRuleForm: React.FC<RuleFormProps> = ({ field, onChange }) => {
|
|
22
|
+
return <Tabs field={field} onChange={onChange} tabs={["range", "one of"]} />;
|
|
49
23
|
};
|
|
50
24
|
|
|
51
|
-
const TemporalRuleForm: React.FC<RuleFormProps> = ({
|
|
52
|
-
field,
|
|
53
|
-
onChange,
|
|
54
|
-
}) => {
|
|
55
|
-
return (
|
|
56
|
-
<Tabs
|
|
57
|
-
field={field}
|
|
58
|
-
onChange={onChange}
|
|
59
|
-
tabs={['one of', 'temporal range']}
|
|
60
|
-
/>
|
|
61
|
-
);
|
|
25
|
+
const TemporalRuleForm: React.FC<RuleFormProps> = ({ field, onChange }) => {
|
|
26
|
+
return <Tabs field={field} onChange={onChange} tabs={["one of", "temporal range"]} />;
|
|
62
27
|
};
|
|
63
28
|
|
|
64
29
|
const EmptyForm: React.FC<RuleFormProps> = () => <React.Fragment />;
|
|
@@ -67,7 +32,7 @@ const FilterEditDialog: React.FC = observer(() => {
|
|
|
67
32
|
const { vizStore } = useGlobalStore();
|
|
68
33
|
const { editingFilterIdx, draggableFieldState } = vizStore;
|
|
69
34
|
|
|
70
|
-
const { t } = useTranslation(
|
|
35
|
+
const { t } = useTranslation("translation", { keyPrefix: "filters" });
|
|
71
36
|
|
|
72
37
|
const field = React.useMemo(() => {
|
|
73
38
|
return editingFilterIdx !== null ? draggableFieldState.filters[editingFilterIdx] : null;
|
|
@@ -83,14 +48,20 @@ const FilterEditDialog: React.FC = observer(() => {
|
|
|
83
48
|
}
|
|
84
49
|
}, [field]);
|
|
85
50
|
|
|
86
|
-
const handleChange = React.useCallback(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
51
|
+
const handleChange = React.useCallback(
|
|
52
|
+
(r: IFilterRule) => {
|
|
53
|
+
if (editingFilterIdx !== null) {
|
|
54
|
+
setUncontrolledField(
|
|
55
|
+
(uf) =>
|
|
56
|
+
({
|
|
57
|
+
...uf,
|
|
58
|
+
rule: r,
|
|
59
|
+
} as IFilterField)
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
[editingFilterIdx]
|
|
64
|
+
);
|
|
94
65
|
|
|
95
66
|
const handleSubmit = React.useCallback(() => {
|
|
96
67
|
if (editingFilterIdx !== null) {
|
|
@@ -100,45 +71,38 @@ const FilterEditDialog: React.FC = observer(() => {
|
|
|
100
71
|
vizStore.closeFilterEditing();
|
|
101
72
|
}, [editingFilterIdx, uncontrolledField]);
|
|
102
73
|
|
|
103
|
-
const Form = field
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
74
|
+
const Form = field
|
|
75
|
+
? ({
|
|
76
|
+
quantitative: QuantitativeRuleForm,
|
|
77
|
+
nominal: NominalRuleForm,
|
|
78
|
+
ordinal: OrdinalRuleForm,
|
|
79
|
+
temporal: TemporalRuleForm,
|
|
80
|
+
}[field.semanticType] as React.FC<RuleFormProps>)
|
|
81
|
+
: EmptyForm;
|
|
82
|
+
|
|
110
83
|
return uncontrolledField ? (
|
|
111
|
-
<Modal
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
{t(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
height="3em"
|
|
131
|
-
role="button"
|
|
132
|
-
tabIndex={0}
|
|
133
|
-
aria-label="ok"
|
|
134
|
-
className="cursor-pointer hover:bg-green-50 p-1"
|
|
135
|
-
onClick={handleSubmit}
|
|
136
|
-
strokeWidth="1.5"
|
|
137
|
-
/>
|
|
84
|
+
<Modal show={Boolean(uncontrolledField)} title={t("editing")} onClose={() => vizStore.closeFilterEditing()}>
|
|
85
|
+
<div className="p-4">
|
|
86
|
+
<h2 className="text-base font-semibold py-2 outline-none">{t("form.name")}</h2>
|
|
87
|
+
<span className="inline-flex items-center rounded-full bg-indigo-100 px-3 py-0.5 text-sm font-medium text-indigo-800">
|
|
88
|
+
{uncontrolledField.name}
|
|
89
|
+
</span>
|
|
90
|
+
<h3 className="text-base font-semibold py-2 outline-none">{t("form.rule")}</h3>
|
|
91
|
+
<Form field={uncontrolledField} onChange={handleChange} />
|
|
92
|
+
<div className="mt-4">
|
|
93
|
+
<PrimaryButton
|
|
94
|
+
onClick={handleSubmit}
|
|
95
|
+
text={t("btn.confirm")}
|
|
96
|
+
/>
|
|
97
|
+
<DefaultButton
|
|
98
|
+
className="ml-2"
|
|
99
|
+
onClick={() => vizStore.closeFilterEditing()}
|
|
100
|
+
text={t("btn.cancel")}
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
138
103
|
</div>
|
|
139
104
|
</Modal>
|
|
140
105
|
) : null;
|
|
141
106
|
});
|
|
142
107
|
|
|
143
|
-
|
|
144
108
|
export default FilterEditDialog;
|
|
@@ -118,7 +118,6 @@ const Slider: React.FC<SliderProps> = React.memo(function Slider ({
|
|
|
118
118
|
};
|
|
119
119
|
|
|
120
120
|
const dragHandler = fromEvent(document.body, 'mousemove').pipe(
|
|
121
|
-
throttleTime(100),
|
|
122
121
|
map(ev => {
|
|
123
122
|
if (!trackRef.current || !dragging) {
|
|
124
123
|
return null;
|
|
@@ -142,6 +141,7 @@ const Slider: React.FC<SliderProps> = React.memo(function Slider ({
|
|
|
142
141
|
|
|
143
142
|
return pos;
|
|
144
143
|
}),
|
|
144
|
+
throttleTime(100),
|
|
145
145
|
filter(pos => {
|
|
146
146
|
return pos !== null && pos !== range[dragging === 'left' ? 0 : 1];
|
|
147
147
|
}),
|
|
@@ -9,6 +9,8 @@ import { baseVis, IReasonType } from "./std2vegaSpec";
|
|
|
9
9
|
import RadioGroupButtons from "./radioGroupButtons";
|
|
10
10
|
import { formatFieldName, mergeMeasures } from "./utils";
|
|
11
11
|
import { useTranslation } from "react-i18next";
|
|
12
|
+
import { useCurrentMediaTheme } from "../utils/media";
|
|
13
|
+
import { builtInThemes } from "../vis/theme";
|
|
12
14
|
|
|
13
15
|
const collection = Insight.IntentionWorkerCollection.init();
|
|
14
16
|
|
|
@@ -41,6 +43,8 @@ const InsightMainBoard: React.FC<InsightMainBoardProps> = (props) => {
|
|
|
41
43
|
const [valueExp, setValueExp] = useState<IMeasureWithStat[]>([]);
|
|
42
44
|
const { t } = useTranslation();
|
|
43
45
|
const container = useRef<HTMLDivElement>(null);
|
|
46
|
+
const mediaTheme = useCurrentMediaTheme();
|
|
47
|
+
const themeConfig = builtInThemes['g2'][mediaTheme];
|
|
44
48
|
|
|
45
49
|
const dimsWithTypes = useMemo(() => {
|
|
46
50
|
const dimensions = fields
|
|
@@ -111,10 +115,13 @@ const InsightMainBoard: React.FC<InsightMainBoardProps> = (props) => {
|
|
|
111
115
|
true
|
|
112
116
|
);
|
|
113
117
|
if (container.current) {
|
|
114
|
-
embed(container.current, _vegaSpec
|
|
118
|
+
embed(container.current, _vegaSpec, {
|
|
119
|
+
actions: false,
|
|
120
|
+
config: themeConfig
|
|
121
|
+
});
|
|
115
122
|
}
|
|
116
123
|
}
|
|
117
|
-
}, [visIndex, recSpaces, visSpaces, fields, dataSource]);
|
|
124
|
+
}, [visIndex, recSpaces, visSpaces, fields, dataSource, themeConfig]);
|
|
118
125
|
|
|
119
126
|
const FilterDesc = useMemo<React.ReactElement[]>(() => {
|
|
120
127
|
if (filters) {
|
package/src/interfaces.ts
CHANGED
package/src/locales/en-US.json
CHANGED
|
@@ -167,7 +167,9 @@
|
|
|
167
167
|
"btn": {
|
|
168
168
|
"select_all": "Select All",
|
|
169
169
|
"unselect_all": "Unselect All",
|
|
170
|
-
"reverse": "Reverse Selection"
|
|
170
|
+
"reverse": "Reverse Selection",
|
|
171
|
+
"confirm": "Confirm",
|
|
172
|
+
"cancel": "Cancel"
|
|
171
173
|
}
|
|
172
174
|
},
|
|
173
175
|
"explain": {
|
|
@@ -187,6 +189,7 @@
|
|
|
187
189
|
"actions": {
|
|
188
190
|
"prev": "Previous",
|
|
189
191
|
"next": "Next",
|
|
190
|
-
"drop_field": "Drop Field Here"
|
|
192
|
+
"drop_field": "Drop Field Here",
|
|
193
|
+
"confirm": "Confirm"
|
|
191
194
|
}
|
|
192
195
|
}
|
package/src/locales/i18n.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { initReactI18next } from 'react-i18next';
|
|
|
3
3
|
import LanguageDetector from 'i18next-browser-languagedetector';
|
|
4
4
|
|
|
5
5
|
import localeEnUs from './en-US.json';
|
|
6
|
+
import localeJaJp from './ja-JP.json';
|
|
6
7
|
import localeZhCn from './zh-CN.json';
|
|
7
8
|
|
|
8
9
|
|
|
@@ -19,6 +20,12 @@ const locales: Resource & { 'en-US': any } = {
|
|
|
19
20
|
'zh-CN': {
|
|
20
21
|
translation: localeZhCn,
|
|
21
22
|
},
|
|
23
|
+
'ja': {
|
|
24
|
+
translation: localeJaJp,
|
|
25
|
+
},
|
|
26
|
+
'ja-JP': {
|
|
27
|
+
translation: localeJaJp,
|
|
28
|
+
}
|
|
22
29
|
} as const;
|
|
23
30
|
|
|
24
31
|
i18n.use(initReactI18next).use(LanguageDetector).init({
|