@kanaries/graphic-walker 0.2.13 → 0.2.14
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 +4 -2
- package/dist/assets/explainer.worker-8428eb12.js.map +1 -1
- package/dist/components/button/base.d.ts +1 -0
- package/dist/components/button/defaultMini.d.ts +4 -0
- package/dist/components/button/primaryMini.d.ts +4 -0
- package/dist/components/dropdownContext/index.d.ts +13 -0
- package/dist/components/dropdownSelect/index.d.ts +17 -0
- package/dist/components/modal.d.ts +1 -0
- package/dist/components/toolbar/toolbar-item.d.ts +1 -0
- package/dist/dataSource/dataSelection/config.d.ts +2 -0
- package/dist/dataSource/index.d.ts +1 -1
- package/dist/fields/datasetFields/dimFields.d.ts +2 -2
- package/dist/fields/datasetFields/meaFields.d.ts +2 -2
- package/dist/fields/encodeFields/singleEncodeDropDown.d.ts +17 -0
- package/dist/fields/encodeFields/singleEncodeEditor.d.ts +11 -0
- package/dist/fields/obComponents/obPill.d.ts +3 -3
- package/dist/graphic-walker.es.js +20874 -19172
- package/dist/graphic-walker.es.js.map +1 -1
- package/dist/graphic-walker.umd.js +245 -170
- package/dist/graphic-walker.umd.js.map +1 -1
- package/dist/insightBoard/index.d.ts +1 -1
- package/dist/interfaces.d.ts +1 -0
- package/dist/renderer/index.d.ts +3 -1
- package/dist/store/visualSpecStore.d.ts +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/media.d.ts +2 -0
- package/dist/vis/react-vega.d.ts +2 -0
- package/dist/vis/theme.d.ts +130 -0
- package/package.json +2 -1
- package/src/App.tsx +8 -5
- package/src/components/button/base.ts +1 -0
- package/src/components/button/default.tsx +6 -2
- package/src/components/button/defaultMini.tsx +17 -0
- package/src/components/button/primary.tsx +6 -2
- package/src/components/button/primaryMini.tsx +21 -0
- package/src/components/callout.tsx +4 -1
- package/src/components/clickMenu.tsx +4 -2
- package/src/components/container.tsx +9 -0
- package/src/components/dataTable/index.tsx +42 -52
- package/src/components/dataTable/pagination.tsx +4 -4
- package/src/components/dataTypeIcon.tsx +1 -1
- package/src/components/dropdownContext/index.tsx +64 -0
- package/src/components/dropdownSelect/index.tsx +92 -0
- package/src/components/modal.tsx +26 -14
- package/src/components/tabs/defaultTab.tsx +4 -4
- package/src/components/tabs/editableTab.tsx +5 -5
- package/src/components/toolbar/components.tsx +18 -1
- package/src/components/toolbar/index.tsx +5 -0
- package/src/components/toolbar/toolbar-button.tsx +6 -1
- package/src/components/toolbar/toolbar-item.tsx +4 -0
- package/src/components/toolbar/toolbar-select-button.tsx +21 -4
- package/src/components/toolbar/toolbar-toggle-button.tsx +6 -1
- package/src/components/tooltip.tsx +4 -1
- package/src/dataSource/dataSelection/config.ts +28 -0
- package/src/dataSource/dataSelection/csvData.tsx +77 -32
- package/src/dataSource/dataSelection/gwFile.tsx +0 -8
- package/src/dataSource/dataSelection/index.tsx +1 -2
- package/src/dataSource/dataSelection/publicData.tsx +2 -3
- package/src/dataSource/index.tsx +81 -61
- package/src/fields/aestheticFields.tsx +3 -1
- package/src/fields/components.tsx +21 -2
- package/src/fields/datasetFields/dimFields.tsx +43 -35
- package/src/fields/datasetFields/index.tsx +2 -2
- package/src/fields/datasetFields/meaFields.tsx +73 -47
- package/src/fields/encodeFields/singleEncodeDropDown.tsx +92 -0
- package/src/fields/encodeFields/singleEncodeEditor.tsx +78 -0
- package/src/fields/filterField/filterEditDialog.tsx +2 -1
- package/src/fields/filterField/filterPill.tsx +1 -1
- package/src/fields/filterField/slider.tsx +1 -1
- package/src/fields/filterField/tabs.tsx +11 -21
- package/src/fields/obComponents/obPill.tsx +65 -35
- package/src/index.css +13 -0
- package/src/insightBoard/index.tsx +24 -23
- package/src/insightBoard/radioGroupButtons.tsx +7 -0
- package/src/interfaces.ts +1 -0
- package/src/lib/inferMeta.ts +1 -1
- package/src/locales/en-US.json +7 -4
- package/src/locales/zh-CN.json +5 -2
- package/src/main.tsx +1 -1
- package/src/renderer/index.tsx +2 -1
- package/src/store/visualSpecStore.ts +16 -0
- package/src/utils/index.ts +19 -0
- package/src/utils/media.ts +26 -0
- package/src/utils/normalization.ts +2 -1
- package/src/vis/react-vega.tsx +20 -4
- package/src/vis/theme.ts +126 -0
- package/src/visualSettings/index.tsx +24 -8
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React, { Fragment } from "react";
|
|
2
|
+
import { Menu, Transition } from "@headlessui/react";
|
|
3
|
+
|
|
4
|
+
function classNames(...classes: string[]) {
|
|
5
|
+
return classes.filter(Boolean).join(" ");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface IDropdownContextOption {
|
|
9
|
+
label: string;
|
|
10
|
+
value: string;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
}
|
|
13
|
+
interface IDropdownContextProps {
|
|
14
|
+
options?: IDropdownContextOption[];
|
|
15
|
+
disable?: boolean;
|
|
16
|
+
onSelect?: (value: string, index: number) => void;
|
|
17
|
+
}
|
|
18
|
+
const DropdownContext: React.FC<IDropdownContextProps> = (props) => {
|
|
19
|
+
const { options = [], disable } = props;
|
|
20
|
+
|
|
21
|
+
if (disable) {
|
|
22
|
+
return <Fragment>{props.children}</Fragment>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<Menu as="span" className="relative block text-left">
|
|
27
|
+
<Menu.Button className="block w-full text-left">{props.children}</Menu.Button>
|
|
28
|
+
|
|
29
|
+
<Transition
|
|
30
|
+
as={Fragment}
|
|
31
|
+
enter="transition ease-out duration-100"
|
|
32
|
+
enterFrom="transform opacity-0 scale-95"
|
|
33
|
+
enterTo="transform opacity-100 scale-100"
|
|
34
|
+
leave="transition ease-in duration-75"
|
|
35
|
+
leaveFrom="transform opacity-100 scale-100"
|
|
36
|
+
leaveTo="transform opacity-0 scale-95"
|
|
37
|
+
>
|
|
38
|
+
<Menu.Items className="absolute left-0 z-50 mt-2 w-56 origin-top-right rounded-md bg-white dark:bg-zinc-900 shadow-lg border border-gray-50 dark:border-gray-800 ring-1 ring-black ring-opacity-5 focus:outline-none">
|
|
39
|
+
<div className="py-1">
|
|
40
|
+
{options.map((option, index) => (
|
|
41
|
+
<Menu.Item key={option.value}>
|
|
42
|
+
{(p) => (
|
|
43
|
+
<span
|
|
44
|
+
className={classNames(
|
|
45
|
+
p.active ? "bg-gray-100 text-gray-900 dark:bg-gray-800 dark:text-gray-50" : "text-gray-700 dark:text-gray-200",
|
|
46
|
+
"block px-4 py-2 text-sm"
|
|
47
|
+
)}
|
|
48
|
+
onClick={() => {
|
|
49
|
+
props.onSelect && !props.disable && props.onSelect(option.value, index);
|
|
50
|
+
}}
|
|
51
|
+
>
|
|
52
|
+
{option.label}
|
|
53
|
+
</span>
|
|
54
|
+
)}
|
|
55
|
+
</Menu.Item>
|
|
56
|
+
))}
|
|
57
|
+
</div>
|
|
58
|
+
</Menu.Items>
|
|
59
|
+
</Transition>
|
|
60
|
+
</Menu>
|
|
61
|
+
);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export default DropdownContext;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import React, { Fragment } from "react";
|
|
2
|
+
import { Listbox, Menu, Transition } from "@headlessui/react";
|
|
3
|
+
import { CheckIcon, ChevronDownIcon, ChevronUpDownIcon } from "@heroicons/react/24/outline";
|
|
4
|
+
|
|
5
|
+
function classNames(...classes: string[]) {
|
|
6
|
+
return classes.filter(Boolean).join(" ");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface IDropdownSelectOption {
|
|
10
|
+
label: string;
|
|
11
|
+
value: string;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
}
|
|
14
|
+
interface IDropdownSelectProps {
|
|
15
|
+
options?: IDropdownSelectOption[];
|
|
16
|
+
disable?: boolean;
|
|
17
|
+
selectedKey: string;
|
|
18
|
+
onSelect?: (value: string) => void;
|
|
19
|
+
placeholder?: string;
|
|
20
|
+
className?: string;
|
|
21
|
+
buttonClassName?: string;
|
|
22
|
+
}
|
|
23
|
+
const DropdownSelect: React.FC<IDropdownSelectProps> = (props) => {
|
|
24
|
+
const { options = [], disable, selectedKey, onSelect, placeholder = "Select an option", className, buttonClassName } = props;
|
|
25
|
+
|
|
26
|
+
const selectedItem = options.find((op) => op.value === selectedKey);
|
|
27
|
+
|
|
28
|
+
if (disable) {
|
|
29
|
+
return <Fragment>{props.children}</Fragment>;
|
|
30
|
+
}
|
|
31
|
+
let rootClassName = "relative";
|
|
32
|
+
let btnComputedClassName = "relative cursor-default text-xs rounded-lg bg-white dark:bg-zinc-900 px-2.5 py-1.5 pr-10 text-left border border-gray-200 dark:border-gray-700 focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 truncate"
|
|
33
|
+
if (buttonClassName) {
|
|
34
|
+
btnComputedClassName = btnComputedClassName + " " + buttonClassName;
|
|
35
|
+
}
|
|
36
|
+
if (className) {
|
|
37
|
+
rootClassName = rootClassName + " " + className;
|
|
38
|
+
}
|
|
39
|
+
return (
|
|
40
|
+
<Listbox
|
|
41
|
+
value={selectedKey}
|
|
42
|
+
onChange={(newKey) => {
|
|
43
|
+
onSelect && onSelect(newKey);
|
|
44
|
+
}}
|
|
45
|
+
>
|
|
46
|
+
<div className={rootClassName}>
|
|
47
|
+
<Listbox.Button className={btnComputedClassName}>
|
|
48
|
+
<span className="block truncate dark:text-white">{selectedItem?.label || ""}</span>
|
|
49
|
+
{ selectedItem === undefined && <span className="block truncate text-gray-400">{placeholder}</span>}
|
|
50
|
+
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
|
51
|
+
<ChevronUpDownIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
|
52
|
+
</span>
|
|
53
|
+
</Listbox.Button>
|
|
54
|
+
<Transition
|
|
55
|
+
as={Fragment}
|
|
56
|
+
leave="transition ease-in duration-100"
|
|
57
|
+
leaveFrom="opacity-100"
|
|
58
|
+
leaveTo="opacity-0"
|
|
59
|
+
>
|
|
60
|
+
<Listbox.Options className="absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white dark:bg-zinc-900 py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
|
61
|
+
{options.map((op, opIndex) => (
|
|
62
|
+
<Listbox.Option
|
|
63
|
+
key={op.value}
|
|
64
|
+
className={({ active }) =>
|
|
65
|
+
`relative cursor-default select-none py-2 pl-10 pr-4 ${
|
|
66
|
+
active ? "bg-amber-100 text-amber-900 dark:bg-amber-800 dark:text-amber-50" : "text-gray-900 dark:text-gray-50"
|
|
67
|
+
}`
|
|
68
|
+
}
|
|
69
|
+
value={op.value}
|
|
70
|
+
>
|
|
71
|
+
{({ selected }) => (
|
|
72
|
+
<>
|
|
73
|
+
<span className={`block truncate ${selected ? "font-medium" : "font-normal"}`}>
|
|
74
|
+
{op.label}
|
|
75
|
+
</span>
|
|
76
|
+
{selected && (
|
|
77
|
+
<span className="absolute inset-y-0 left-0 flex items-center pl-3 text-amber-600 dark:text-amber-400">
|
|
78
|
+
<CheckIcon className="h-5 w-5" aria-hidden="true" />
|
|
79
|
+
</span>
|
|
80
|
+
)}
|
|
81
|
+
</>
|
|
82
|
+
)}
|
|
83
|
+
</Listbox.Option>
|
|
84
|
+
))}
|
|
85
|
+
</Listbox.Options>
|
|
86
|
+
</Transition>
|
|
87
|
+
</div>
|
|
88
|
+
</Listbox>
|
|
89
|
+
);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export default DropdownSelect;
|
package/src/components/modal.tsx
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import React, { useRef } from "react";
|
|
2
2
|
import styled from "styled-components";
|
|
3
3
|
import { XCircleIcon } from "@heroicons/react/24/outline";
|
|
4
|
+
import { Fragment, useState } from "react";
|
|
5
|
+
import { Dialog, Transition } from "@headlessui/react";
|
|
6
|
+
import { ExclamationTriangleIcon, XMarkIcon } from "@heroicons/react/24/outline";
|
|
4
7
|
|
|
5
8
|
const Background = styled.div({
|
|
6
9
|
position: "fixed",
|
|
@@ -28,27 +31,33 @@ const Container = styled.div`
|
|
|
28
31
|
padding: 12px;
|
|
29
32
|
font-size: 14px;
|
|
30
33
|
align-items: center;
|
|
34
|
+
@media (prefers-color-scheme: dark) {
|
|
35
|
+
background-color: #000;
|
|
36
|
+
}
|
|
31
37
|
}
|
|
32
38
|
> div.container {
|
|
33
|
-
padding: 1em;
|
|
39
|
+
padding: 0.5em 1em 1em 1em;
|
|
34
40
|
}
|
|
35
41
|
position: fixed;
|
|
36
42
|
left: 50%;
|
|
37
43
|
top: 50%;
|
|
38
44
|
transform: translate(-50%, -50%);
|
|
39
45
|
background-color: #fff;
|
|
46
|
+
@media (prefers-color-scheme: dark) {
|
|
47
|
+
background-color: #000;
|
|
48
|
+
}
|
|
40
49
|
/* box-shadow: 0px 0px 12px 3px rgba(0, 0, 0, 0.19); */
|
|
41
50
|
border-radius: 4px;
|
|
42
51
|
z-index: 999;
|
|
43
52
|
`;
|
|
44
53
|
interface ModalProps {
|
|
45
54
|
onClose?: () => void;
|
|
55
|
+
show?: boolean;
|
|
46
56
|
title?: string;
|
|
47
57
|
}
|
|
48
58
|
const Modal: React.FC<ModalProps> = (props) => {
|
|
49
|
-
const { onClose, title } = props;
|
|
59
|
+
const { onClose, title, show } = props;
|
|
50
60
|
const prevMouseDownTimeRef = useRef(0);
|
|
51
|
-
|
|
52
61
|
return (
|
|
53
62
|
<Background
|
|
54
63
|
// This is a safer replacement of onClick handler.
|
|
@@ -56,7 +65,7 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
56
65
|
// at a different element and then released when the mouse is moved on the target element.
|
|
57
66
|
// This case is required to be prevented, especially disturbing when interacting
|
|
58
67
|
// with a Slider component.
|
|
59
|
-
className="border border-gray-300"
|
|
68
|
+
className={"border border-gray-300 dark:border-gray-600 " + (show ? "block" : "hidden")}
|
|
60
69
|
onMouseDown={() => (prevMouseDownTimeRef.current = Date.now())}
|
|
61
70
|
onMouseOut={() => (prevMouseDownTimeRef.current = 0)}
|
|
62
71
|
onMouseUp={() => {
|
|
@@ -65,17 +74,20 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
65
74
|
}
|
|
66
75
|
}}
|
|
67
76
|
>
|
|
68
|
-
<Container role="dialog" className="shadow-lg" onMouseDown={(e) => e.stopPropagation()}>
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
<Container role="dialog" className="shadow-lg rounded-md border border-gray-100 dark:border-gray-800" onMouseDown={(e) => e.stopPropagation()}>
|
|
78
|
+
<div className="absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
|
|
79
|
+
<button
|
|
80
|
+
type="button"
|
|
81
|
+
className="rounded-md bg-white dark:bg-zinc-900 text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
|
82
|
+
onClick={() => {
|
|
83
|
+
onClose?.();
|
|
84
|
+
}}
|
|
85
|
+
>
|
|
86
|
+
<span className="sr-only">Close</span>
|
|
87
|
+
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
|
|
88
|
+
</button>
|
|
78
89
|
</div>
|
|
90
|
+
<div className="px-6 pt-4 text-base font-semibold leading-6 text-gray-900 dark:text-gray-50">{title}</div>
|
|
79
91
|
<div className="container">{props.children}</div>
|
|
80
92
|
</Container>
|
|
81
93
|
</Background>
|
|
@@ -19,7 +19,7 @@ export default function Default(props: DefaultProps) {
|
|
|
19
19
|
const { tabs, selectedKey, onSelected } = props;
|
|
20
20
|
|
|
21
21
|
return (
|
|
22
|
-
<div className="border-b border-gray-200 mb-2" >
|
|
22
|
+
<div className="border-b border-gray-200 dark:border-gray-700 mb-2" >
|
|
23
23
|
<nav className="-mb-px flex space-x-8" role="tablist" aria-label="Tabs">
|
|
24
24
|
{tabs.map((tab, tabIndex) => (
|
|
25
25
|
<span
|
|
@@ -31,9 +31,9 @@ export default function Default(props: DefaultProps) {
|
|
|
31
31
|
key={tab.key}
|
|
32
32
|
className={classNames(
|
|
33
33
|
tab.key === selectedKey
|
|
34
|
-
? 'border-indigo-500 text-indigo-600'
|
|
35
|
-
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300',
|
|
36
|
-
'whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm'
|
|
34
|
+
? 'border-indigo-500 text-indigo-600 dark:border-indigo-400 dark:text-indigo-300'
|
|
35
|
+
: 'border-transparent text-gray-500 hover:text-gray-700 dark:hover:text-gray-200 hover:border-gray-300 dark:text-gray-400',
|
|
36
|
+
'whitespace-nowrap py-2 px-1 border-b-2 font-medium text-sm cursor-pointer'
|
|
37
37
|
)}
|
|
38
38
|
>{tab.label}</span>
|
|
39
39
|
))}
|
|
@@ -32,8 +32,8 @@ export default function EditableTabs(props: EditableTabsProps) {
|
|
|
32
32
|
}, [clearEditStatus]);
|
|
33
33
|
|
|
34
34
|
return (
|
|
35
|
-
<div className="border-b border-gray-200 overflow-x-auto overflow-y-hidden" onMouseLeave={clearEditStatus}>
|
|
36
|
-
<nav className="-mb-px flex h-8 border-gray-200 border-
|
|
35
|
+
<div className="border-b border-gray-200 dark:border-gray-800 overflow-x-auto overflow-y-hidden" onMouseLeave={clearEditStatus}>
|
|
36
|
+
<nav className="-mb-px flex h-8 border-gray-200 dark:border-gray-800" role="tablist" aria-label="Tabs">
|
|
37
37
|
{tabs.map((tab, tabIndex) => (
|
|
38
38
|
<span
|
|
39
39
|
role="tab"
|
|
@@ -64,9 +64,9 @@ export default function EditableTabs(props: EditableTabsProps) {
|
|
|
64
64
|
key={tab.key}
|
|
65
65
|
className={classNames(
|
|
66
66
|
tab.key === selectedKey
|
|
67
|
-
? "
|
|
68
|
-
: "text-gray-500 hover:text-gray-700",
|
|
69
|
-
"whitespace-nowrap border-gray-200 py-1 px-2
|
|
67
|
+
? "border rounded-t"
|
|
68
|
+
: "text-gray-500 dark:text-gray-400 hover:text-gray-700 hover:bg-gray-50 dark:hover:text-gray-200 dark:hover:bg-gray-800",
|
|
69
|
+
"whitespace-nowrap border-gray-200 dark:border-gray-700 py-1 px-2 pr-6 text-sm cursor-pointer dark:text-white"
|
|
70
70
|
)}
|
|
71
71
|
/>
|
|
72
72
|
))}
|
|
@@ -46,6 +46,14 @@ export const ToolbarContainer = styled.div`
|
|
|
46
46
|
height: var(--height);
|
|
47
47
|
background-color: var(--background-color);
|
|
48
48
|
color: var(--color);
|
|
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
|
+
}
|
|
49
57
|
/* box-shadow: 0px 1px 3px 1px rgba(136, 136, 136, 0.1); */
|
|
50
58
|
border-radius: 2px;
|
|
51
59
|
overflow: hidden;
|
|
@@ -94,7 +102,6 @@ export const ToolbarItemContainerElement = styled.div<{ split: boolean }>`
|
|
|
94
102
|
&[aria-disabled=false] {
|
|
95
103
|
cursor: pointer;
|
|
96
104
|
:hover, :focus, &.open {
|
|
97
|
-
background-image: linear-gradient(#FFFFFFCC, #FEFEFECC);
|
|
98
105
|
--background-color: #FEFEFE;
|
|
99
106
|
color: var(--color-hover);
|
|
100
107
|
&.split * svg {
|
|
@@ -104,6 +111,16 @@ export const ToolbarItemContainerElement = styled.div<{ split: boolean }>`
|
|
|
104
111
|
& svg {
|
|
105
112
|
text-shadow: 0 0 1.5px var(--shadow-color);
|
|
106
113
|
}
|
|
114
|
+
background-color: var(--background-color);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
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
|
+
}
|
|
107
124
|
}
|
|
108
125
|
}
|
|
109
126
|
transition: color 100ms, background-image 100ms;
|
|
@@ -11,6 +11,11 @@ const Root = styled.div`
|
|
|
11
11
|
--color-hover: #555;
|
|
12
12
|
--blue: #282958;
|
|
13
13
|
--blue-dark: #1d1e38;
|
|
14
|
+
--dark-mode-background-color: #1f1f1f;
|
|
15
|
+
--dark-mode-color: #aaa;
|
|
16
|
+
--dark-mode-color-hover: #ccc;
|
|
17
|
+
--dark-mode-blue: #282958;
|
|
18
|
+
--dark-mode-blue-dark: #1d1e38;
|
|
14
19
|
`;
|
|
15
20
|
|
|
16
21
|
export interface ToolbarProps {
|
|
@@ -12,13 +12,18 @@ const ToolbarButton = memo<IToolbarProps<ToolbarButtonItem>>(function ToolbarBut
|
|
|
12
12
|
const { icon: Icon, label, disabled, onClick } = item;
|
|
13
13
|
const handlers = useHandlers(() => onClick?.(), disabled ?? false);
|
|
14
14
|
|
|
15
|
+
const mergedIconStyles = {
|
|
16
|
+
...styles?.icon,
|
|
17
|
+
...item.styles?.icon,
|
|
18
|
+
};
|
|
19
|
+
|
|
15
20
|
return (
|
|
16
21
|
<>
|
|
17
22
|
<ToolbarItemContainer
|
|
18
23
|
props={props}
|
|
19
24
|
handlers={onClick ? handlers : null}
|
|
20
25
|
>
|
|
21
|
-
<Icon style={
|
|
26
|
+
<Icon style={mergedIconStyles} />
|
|
22
27
|
</ToolbarItemContainer>
|
|
23
28
|
</>
|
|
24
29
|
);
|
|
@@ -35,6 +35,9 @@ const FormContainer = styled(ToolbarContainer)`
|
|
|
35
35
|
width: max-content;
|
|
36
36
|
height: max-content;
|
|
37
37
|
background-color: #fff;
|
|
38
|
+
@media (prefers-color-scheme: dark) {
|
|
39
|
+
background-color: #000;
|
|
40
|
+
}
|
|
38
41
|
`;
|
|
39
42
|
|
|
40
43
|
export interface IToolbarItem {
|
|
@@ -48,6 +51,7 @@ export interface IToolbarItem {
|
|
|
48
51
|
disabled?: boolean;
|
|
49
52
|
menu?: ToolbarProps;
|
|
50
53
|
form?: JSX.Element;
|
|
54
|
+
styles?: Partial<Pick<NonNullable<ToolbarProps['styles']>, 'item' | 'icon' | 'splitIcon'>>;
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
export const ToolbarItemSplitter = '-';
|
|
@@ -16,7 +16,21 @@ const OptionGroup = styled(ToolbarContainer)`
|
|
|
16
16
|
--color: #777;
|
|
17
17
|
--color-hover: #555;
|
|
18
18
|
--blue: #282958;
|
|
19
|
+
--dark-mode-background-color: #1f1f1f;
|
|
20
|
+
--dark-mode-background-color-hover: #2f2f2f;
|
|
21
|
+
--dark-mode-color: #aaa;
|
|
22
|
+
--dark-mode-color-hover: #ccc;
|
|
23
|
+
--dark-mode-blue: #282958;
|
|
19
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
|
+
}
|
|
20
34
|
`;
|
|
21
35
|
|
|
22
36
|
const Option = styled(ToolbarItemContainerElement)`
|
|
@@ -115,6 +129,11 @@ const ToolbarSelectButton = memo<IToolbarProps<ToolbarSelectButtonItem>>(functio
|
|
|
115
129
|
const currentOption = options.find(opt => opt.key === value);
|
|
116
130
|
const CurrentIcon = currentOption?.icon;
|
|
117
131
|
|
|
132
|
+
const mergedIconStyles = {
|
|
133
|
+
...styles?.icon,
|
|
134
|
+
...item.styles?.icon,
|
|
135
|
+
};
|
|
136
|
+
|
|
118
137
|
return (
|
|
119
138
|
<>
|
|
120
139
|
<ToolbarItemContainer
|
|
@@ -126,20 +145,18 @@ const ToolbarSelectButton = memo<IToolbarProps<ToolbarSelectButtonItem>>(functio
|
|
|
126
145
|
handlers={handlers}
|
|
127
146
|
aria-haspopup="listbox"
|
|
128
147
|
>
|
|
129
|
-
<Icon style={
|
|
148
|
+
<Icon style={mergedIconStyles} />
|
|
130
149
|
{CurrentIcon && (
|
|
131
150
|
<CurrentIcon
|
|
132
151
|
style={{
|
|
133
|
-
...styles?.icon,
|
|
134
152
|
position: 'absolute',
|
|
135
153
|
left: 'calc(var(--height) - var(--icon-size) * 1.2)',
|
|
136
154
|
bottom: 'calc((var(--height) - var(--icon-size)) * 0.1)',
|
|
137
155
|
width: 'calc(var(--icon-size) * 0.6)',
|
|
138
156
|
height: 'calc(var(--icon-size) * 0.6)',
|
|
139
157
|
margin: 'calc((var(--height) - var(--icon-size)) * 0.2)',
|
|
140
|
-
filter: 'drop-shadow(0 0 0.5px var(--background-color)) '.repeat(4),
|
|
141
158
|
pointerEvents: 'none',
|
|
142
|
-
|
|
159
|
+
...mergedIconStyles,
|
|
143
160
|
}}
|
|
144
161
|
/>
|
|
145
162
|
)}
|
|
@@ -50,6 +50,11 @@ const ToolbarToggleButton = memo<IToolbarProps<ToolbarToggleButtonItem>>(functio
|
|
|
50
50
|
const { icon: Icon, label, disabled, checked, onChange } = item;
|
|
51
51
|
const handlers = useHandlers(() => onChange(!checked), disabled ?? false);
|
|
52
52
|
|
|
53
|
+
const mergedIconStyles = {
|
|
54
|
+
...styles?.icon,
|
|
55
|
+
...item.styles?.icon,
|
|
56
|
+
};
|
|
57
|
+
|
|
53
58
|
return (
|
|
54
59
|
<>
|
|
55
60
|
<ToolbarItemContainer
|
|
@@ -59,7 +64,7 @@ const ToolbarToggleButton = memo<IToolbarProps<ToolbarToggleButtonItem>>(functio
|
|
|
59
64
|
aria-checked={checked}
|
|
60
65
|
>
|
|
61
66
|
<ToggleContainer checked={checked}>
|
|
62
|
-
<Icon style={
|
|
67
|
+
<Icon style={mergedIconStyles} />
|
|
63
68
|
</ToggleContainer>
|
|
64
69
|
</ToolbarItemContainer>
|
|
65
70
|
</>
|
|
@@ -32,6 +32,9 @@ const Bubble = styled.div`
|
|
|
32
32
|
height: 8px;
|
|
33
33
|
transform: translate(-50%, 50%) rotate(45deg);
|
|
34
34
|
background-color: #fff;
|
|
35
|
+
@media (prefers-color-scheme: dark) {
|
|
36
|
+
background-color: #000;
|
|
37
|
+
}
|
|
35
38
|
border-radius: 1px;
|
|
36
39
|
}
|
|
37
40
|
`;
|
|
@@ -125,7 +128,7 @@ const Tooltip = memo<TooltipProps>(function Tooltip({
|
|
|
125
128
|
root &&
|
|
126
129
|
createPortal(
|
|
127
130
|
<Bubble
|
|
128
|
-
className="fixed text-xs p-1 px-3 text-gray-500 bg-white z-50"
|
|
131
|
+
className="fixed text-xs p-1 px-3 text-gray-500 bg-white dark:bg-zinc-900 z-50"
|
|
129
132
|
onMouseOver={() => setHover(true)}
|
|
130
133
|
onMouseOut={() => setHover(false)}
|
|
131
134
|
style={{ left: pos[0], top: pos[1] - 4 }}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { IDropdownSelectOption } from "../../components/dropdownSelect";
|
|
2
|
+
|
|
3
|
+
export const charsetOptions: IDropdownSelectOption[] = [
|
|
4
|
+
{
|
|
5
|
+
label: 'UTF-8',
|
|
6
|
+
value: 'utf-8',
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
label: 'GB2312',
|
|
10
|
+
value: 'gb2312',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
label: 'US-ASCII',
|
|
14
|
+
value: 'us-ascii',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
label: 'Big5',
|
|
18
|
+
value: 'big5',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
label: 'Big5-HKSCS',
|
|
22
|
+
value: 'Big5-HKSCS',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
label: 'GB18030',
|
|
26
|
+
value: 'GB18030',
|
|
27
|
+
},
|
|
28
|
+
]
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useRef, useCallback } from "react";
|
|
1
|
+
import React, { useRef, useCallback, useState } from "react";
|
|
2
2
|
import { FileReader } from "@kanaries/web-data-loader";
|
|
3
3
|
import { IRow } from "../../interfaces";
|
|
4
4
|
import Table from "../table";
|
|
@@ -8,25 +8,51 @@ import { observer } from "mobx-react-lite";
|
|
|
8
8
|
import { useTranslation } from "react-i18next";
|
|
9
9
|
import DefaultButton from "../../components/button/default";
|
|
10
10
|
import PrimaryButton from "../../components/button/primary";
|
|
11
|
+
import DropdownSelect from "../../components/dropdownSelect";
|
|
12
|
+
import { charsetOptions } from "./config";
|
|
11
13
|
|
|
12
14
|
const Container = styled.div`
|
|
13
15
|
overflow-x: auto;
|
|
16
|
+
min-height: 300px;
|
|
14
17
|
`;
|
|
15
18
|
|
|
16
19
|
interface ICSVData {}
|
|
17
20
|
const CSVData: React.FC<ICSVData> = (props) => {
|
|
18
21
|
const fileRef = useRef<HTMLInputElement>(null);
|
|
19
22
|
const { commonStore } = useGlobalStore();
|
|
20
|
-
const { tmpDSName, tmpDataSource } = commonStore;
|
|
23
|
+
const { tmpDSName, tmpDataSource, tmpDSRawFields } = commonStore;
|
|
24
|
+
const [encoding, setEncoding] = useState<string>("utf-8");
|
|
21
25
|
|
|
22
26
|
const onSubmitData = useCallback(() => {
|
|
23
27
|
commonStore.commitTempDS();
|
|
24
28
|
}, []);
|
|
25
29
|
|
|
26
30
|
const { t } = useTranslation("translation", { keyPrefix: "DataSource.dialog.file" });
|
|
31
|
+
const fileLoaded = tmpDataSource.length > 0 && tmpDSRawFields.length > 0;
|
|
27
32
|
|
|
28
33
|
return (
|
|
29
34
|
<Container>
|
|
35
|
+
{!fileLoaded && (
|
|
36
|
+
<div className="text-center">
|
|
37
|
+
<svg
|
|
38
|
+
className="mx-auto h-12 w-12 text-gray-400"
|
|
39
|
+
fill="none"
|
|
40
|
+
viewBox="0 0 24 24"
|
|
41
|
+
stroke="currentColor"
|
|
42
|
+
aria-hidden="true"
|
|
43
|
+
>
|
|
44
|
+
<path
|
|
45
|
+
vectorEffect="non-scaling-stroke"
|
|
46
|
+
strokeLinecap="round"
|
|
47
|
+
strokeLinejoin="round"
|
|
48
|
+
strokeWidth={2}
|
|
49
|
+
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
|
+
/>
|
|
51
|
+
</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>
|
|
54
|
+
</div>
|
|
55
|
+
)}
|
|
30
56
|
<input
|
|
31
57
|
style={{ display: "none" }}
|
|
32
58
|
type="file"
|
|
@@ -39,42 +65,61 @@ const CSVData: React.FC<ICSVData> = (props) => {
|
|
|
39
65
|
file,
|
|
40
66
|
config: { type: "reservoirSampling", size: Infinity },
|
|
41
67
|
onLoading: () => {},
|
|
68
|
+
encoding,
|
|
42
69
|
}).then((data) => {
|
|
43
70
|
commonStore.updateTempDS(data as IRow[]);
|
|
44
71
|
});
|
|
45
72
|
}
|
|
46
73
|
}}
|
|
47
74
|
/>
|
|
48
|
-
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
{!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);
|
|
93
|
+
}}
|
|
94
|
+
/>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
)}
|
|
98
|
+
{fileLoaded && (
|
|
99
|
+
<div className="mb-2 mt-6">
|
|
100
|
+
<label className="block text-xs text-gray-800 dark:text-gray-200 mb-1 font-bold">
|
|
101
|
+
{t("dataset_name")}
|
|
102
|
+
</label>
|
|
103
|
+
<input
|
|
104
|
+
type="text"
|
|
105
|
+
placeholder={t("dataset_name")}
|
|
106
|
+
value={tmpDSName}
|
|
107
|
+
onChange={(e) => {
|
|
108
|
+
commonStore.updateTempName(e.target.value);
|
|
109
|
+
}}
|
|
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"
|
|
111
|
+
/>
|
|
112
|
+
<PrimaryButton
|
|
113
|
+
className="mr-2"
|
|
114
|
+
text={t("submit")}
|
|
115
|
+
disabled={tmpDataSource.length === 0}
|
|
116
|
+
onClick={() => {
|
|
117
|
+
onSubmitData();
|
|
118
|
+
}}
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
)}
|
|
122
|
+
{fileLoaded && <Table />}
|
|
78
123
|
</Container>
|
|
79
124
|
);
|
|
80
125
|
};
|