@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.
Files changed (87) hide show
  1. package/dist/App.d.ts +4 -2
  2. package/dist/assets/explainer.worker-8428eb12.js.map +1 -1
  3. package/dist/components/button/base.d.ts +1 -0
  4. package/dist/components/button/defaultMini.d.ts +4 -0
  5. package/dist/components/button/primaryMini.d.ts +4 -0
  6. package/dist/components/dropdownContext/index.d.ts +13 -0
  7. package/dist/components/dropdownSelect/index.d.ts +17 -0
  8. package/dist/components/modal.d.ts +1 -0
  9. package/dist/components/toolbar/toolbar-item.d.ts +1 -0
  10. package/dist/dataSource/dataSelection/config.d.ts +2 -0
  11. package/dist/dataSource/index.d.ts +1 -1
  12. package/dist/fields/datasetFields/dimFields.d.ts +2 -2
  13. package/dist/fields/datasetFields/meaFields.d.ts +2 -2
  14. package/dist/fields/encodeFields/singleEncodeDropDown.d.ts +17 -0
  15. package/dist/fields/encodeFields/singleEncodeEditor.d.ts +11 -0
  16. package/dist/fields/obComponents/obPill.d.ts +3 -3
  17. package/dist/graphic-walker.es.js +20874 -19172
  18. package/dist/graphic-walker.es.js.map +1 -1
  19. package/dist/graphic-walker.umd.js +245 -170
  20. package/dist/graphic-walker.umd.js.map +1 -1
  21. package/dist/insightBoard/index.d.ts +1 -1
  22. package/dist/interfaces.d.ts +1 -0
  23. package/dist/renderer/index.d.ts +3 -1
  24. package/dist/store/visualSpecStore.d.ts +1 -0
  25. package/dist/utils/index.d.ts +2 -0
  26. package/dist/utils/media.d.ts +2 -0
  27. package/dist/vis/react-vega.d.ts +2 -0
  28. package/dist/vis/theme.d.ts +130 -0
  29. package/package.json +2 -1
  30. package/src/App.tsx +8 -5
  31. package/src/components/button/base.ts +1 -0
  32. package/src/components/button/default.tsx +6 -2
  33. package/src/components/button/defaultMini.tsx +17 -0
  34. package/src/components/button/primary.tsx +6 -2
  35. package/src/components/button/primaryMini.tsx +21 -0
  36. package/src/components/callout.tsx +4 -1
  37. package/src/components/clickMenu.tsx +4 -2
  38. package/src/components/container.tsx +9 -0
  39. package/src/components/dataTable/index.tsx +42 -52
  40. package/src/components/dataTable/pagination.tsx +4 -4
  41. package/src/components/dataTypeIcon.tsx +1 -1
  42. package/src/components/dropdownContext/index.tsx +64 -0
  43. package/src/components/dropdownSelect/index.tsx +92 -0
  44. package/src/components/modal.tsx +26 -14
  45. package/src/components/tabs/defaultTab.tsx +4 -4
  46. package/src/components/tabs/editableTab.tsx +5 -5
  47. package/src/components/toolbar/components.tsx +18 -1
  48. package/src/components/toolbar/index.tsx +5 -0
  49. package/src/components/toolbar/toolbar-button.tsx +6 -1
  50. package/src/components/toolbar/toolbar-item.tsx +4 -0
  51. package/src/components/toolbar/toolbar-select-button.tsx +21 -4
  52. package/src/components/toolbar/toolbar-toggle-button.tsx +6 -1
  53. package/src/components/tooltip.tsx +4 -1
  54. package/src/dataSource/dataSelection/config.ts +28 -0
  55. package/src/dataSource/dataSelection/csvData.tsx +77 -32
  56. package/src/dataSource/dataSelection/gwFile.tsx +0 -8
  57. package/src/dataSource/dataSelection/index.tsx +1 -2
  58. package/src/dataSource/dataSelection/publicData.tsx +2 -3
  59. package/src/dataSource/index.tsx +81 -61
  60. package/src/fields/aestheticFields.tsx +3 -1
  61. package/src/fields/components.tsx +21 -2
  62. package/src/fields/datasetFields/dimFields.tsx +43 -35
  63. package/src/fields/datasetFields/index.tsx +2 -2
  64. package/src/fields/datasetFields/meaFields.tsx +73 -47
  65. package/src/fields/encodeFields/singleEncodeDropDown.tsx +92 -0
  66. package/src/fields/encodeFields/singleEncodeEditor.tsx +78 -0
  67. package/src/fields/filterField/filterEditDialog.tsx +2 -1
  68. package/src/fields/filterField/filterPill.tsx +1 -1
  69. package/src/fields/filterField/slider.tsx +1 -1
  70. package/src/fields/filterField/tabs.tsx +11 -21
  71. package/src/fields/obComponents/obPill.tsx +65 -35
  72. package/src/index.css +13 -0
  73. package/src/insightBoard/index.tsx +24 -23
  74. package/src/insightBoard/radioGroupButtons.tsx +7 -0
  75. package/src/interfaces.ts +1 -0
  76. package/src/lib/inferMeta.ts +1 -1
  77. package/src/locales/en-US.json +7 -4
  78. package/src/locales/zh-CN.json +5 -2
  79. package/src/main.tsx +1 -1
  80. package/src/renderer/index.tsx +2 -1
  81. package/src/store/visualSpecStore.ts +16 -0
  82. package/src/utils/index.ts +19 -0
  83. package/src/utils/media.ts +26 -0
  84. package/src/utils/normalization.ts +2 -1
  85. package/src/vis/react-vega.tsx +20 -4
  86. package/src/vis/theme.ts +126 -0
  87. package/src/visualSettings/index.tsx +24 -8
@@ -1,15 +1,7 @@
1
1
  import React, { useRef, useCallback } from "react";
2
- import { FileReader } from "@kanaries/web-data-loader";
3
- import { IRow } from "../../interfaces";
4
- import Table from "../table";
5
- import styled from "styled-components";
6
2
  import { useGlobalStore } from "../../store";
7
3
  import { observer } from "mobx-react-lite";
8
- import { useTranslation } from "react-i18next";
9
4
 
10
- const Container = styled.div`
11
- overflow-x: auto;
12
- `;
13
5
 
14
6
  interface GWFileProps {
15
7
  fileRef: React.RefObject<HTMLInputElement>;
@@ -3,7 +3,7 @@ import { useState } from "react";
3
3
  import CSVData from "./csvData";
4
4
  import PublicData from "./publicData";
5
5
  import { useTranslation } from "react-i18next";
6
- import PureTabs from "../../components/tabs/editableTab";
6
+ import PureTabs from "../../components/tabs/defaultTab";
7
7
 
8
8
  const DataSelection: React.FC = (props) => {
9
9
  const [sourceType, setSourceType] = useState<"file" | "public">("file");
@@ -26,7 +26,6 @@ const DataSelection: React.FC = (props) => {
26
26
  setSourceType(sk as "public" | "file");
27
27
  }}
28
28
  />
29
- <hr className="mt-1 mb-1" />
30
29
  {sourceType === "file" && <CSVData />}
31
30
  {sourceType === "public" && <PublicData />}
32
31
  </div>
@@ -36,20 +36,19 @@ const PublicData: React.FC<IPublicDataProps> = props => {
36
36
  })
37
37
  })
38
38
  }}
39
- className="border rounded border-gray-300 p-2 m-2 cursor-pointer hover:bg-gray-50"
39
+ className="border rounded border-gray-300 dark:border-gray-600 p-2 m-2 cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800 dark:text-gray-200"
40
40
  >
41
41
  <div>{data.title}</div>
42
42
  {/* <p>{data.title}</p> */}
43
43
  </div>)
44
44
  }
45
45
  </div>
46
- <hr className="m-1" />
47
46
  <PrimaryButton
47
+ className='my-1'
48
48
  disabled={tmpDataSource.length === 0}
49
49
  onClick={() => { commonStore.commitTempDS() }}
50
50
  text={t('submit')}
51
51
  />
52
- <hr className="m-1" />
53
52
  <Table />
54
53
  </div>
55
54
  }
@@ -1,20 +1,22 @@
1
- import React, { useRef } from 'react';
2
- import { observer } from 'mobx-react-lite';
3
- import { CheckCircleIcon, ArrowPathIcon } from '@heroicons/react/24/outline';
4
- import { useTranslation } from 'react-i18next';
5
- import { Container } from '../components/container';
6
- import Modal from '../components/modal';
7
- import { useGlobalStore } from '../store';
8
- import { download } from '../utils/save';
9
- import GwFile from './dataSelection/gwFile';
10
- import DataSelection from './dataSelection';
11
- import DefaultButton from '../components/button/default';
1
+ import React, { useRef } from "react";
2
+ import { observer } from "mobx-react-lite";
3
+ import { CheckCircleIcon, ArrowPathIcon } from "@heroicons/react/24/outline";
4
+ import { useTranslation } from "react-i18next";
5
+ import { Container } from "../components/container";
6
+ import Modal from "../components/modal";
7
+ import { useGlobalStore } from "../store";
8
+ import { download } from "../utils/save";
9
+ import GwFile from "./dataSelection/gwFile";
10
+ import DataSelection from "./dataSelection";
11
+ import DefaultButton from "../components/button/default";
12
+ import DropdownSelect from "../components/dropdownSelect";
13
+ import PrimaryButton from "../components/button/primary";
12
14
 
13
15
  interface DSSegmentProps {
14
16
  preWorkDone: boolean;
15
17
  }
16
18
 
17
- const DataSourceSegment: React.FC<DSSegmentProps> = props => {
19
+ const DataSourceSegment: React.FC<DSSegmentProps> = (props) => {
18
20
  const { preWorkDone } = props;
19
21
  const { commonStore, vizStore } = useGlobalStore();
20
22
  const gwFileRef = useRef<HTMLInputElement>(null);
@@ -22,55 +24,73 @@ const DataSourceSegment: React.FC<DSSegmentProps> = props => {
22
24
 
23
25
  const { currentDataset, datasets, showDSPanel } = commonStore;
24
26
 
25
- return <Container className="flex flex-row items-stretch">
26
- <GwFile fileRef={gwFileRef} />
27
- {!preWorkDone && <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>}
28
- <label className="text-xs mr-1 whitespace-nowrap self-center h-4">
29
- {t('DataSource.labels.cur_dataset')}
30
- </label>
31
- <select
32
- className="border border-gray-500 rounded-sm text-xs pt-0.5 pb-0.5 pl-2 pr-2"
33
- value={currentDataset.id}
34
- onChange={(e) => { commonStore.useDS(e.target.value); }}
35
- >
36
- {datasets.map((ds) => (
37
- <option value={ds.id} key={ds.id}>
38
- {ds.name}
39
- </option>
40
- ))}
41
- </select>
27
+ return (
28
+ <Container className="flex items-center">
29
+ <GwFile fileRef={gwFileRef} />
30
+ {!preWorkDone && (
31
+ <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>
32
+ )}
33
+ {/* <label className="text-xs mr-1 whitespace-nowrap self-center h-4">
34
+ {t("DataSource.labels.cur_dataset")}
35
+ </label> */}
36
+ <div className="mr-2">
37
+ <DropdownSelect
38
+ options={datasets.map((d) => ({ label: d.name, value: d.id }))}
39
+ selectedKey={currentDataset.id}
40
+ onSelect={(dsKey) => {
41
+ commonStore.useDS(dsKey);
42
+ }}
43
+ placeholder={t("DataSource.labels.cur_dataset")}
44
+ />
45
+ </div>
42
46
 
43
- <DefaultButton
44
- text={t('DataSource.buttons.create_dataset')}
45
- onClick={() => { commonStore.startDSBuildingTask() }}
46
- />
47
- <DefaultButton
48
- text={t('DataSource.buttons.export_as_file')}
49
- onClick={() => {
50
- const res = vizStore.exportAsRaw();
51
- download(res, 'graphic-walker-notebook.json', 'text/plain')
52
- }}
53
- />
54
- <DefaultButton
55
- text={t('DataSource.buttons.import_file')}
56
- onClick={() => {
57
- if (gwFileRef.current) {
58
- gwFileRef.current.click();
59
- }
60
- }}
61
- />
62
- {showDSPanel && (
47
+ <PrimaryButton
48
+ className="mr-2"
49
+ text={t("DataSource.buttons.create_dataset")}
50
+ onClick={() => {
51
+ commonStore.startDSBuildingTask();
52
+ }}
53
+ />
54
+ <DefaultButton
55
+ className="mr-2"
56
+ text={t("DataSource.buttons.export_as_file")}
57
+ onClick={() => {
58
+ const res = vizStore.exportAsRaw();
59
+ download(res, "graphic-walker-notebook.json", "text/plain");
60
+ }}
61
+ />
62
+ <DefaultButton
63
+ className="mr-2"
64
+ text={t("DataSource.buttons.import_file")}
65
+ onClick={() => {
66
+ if (gwFileRef.current) {
67
+ gwFileRef.current.click();
68
+ }
69
+ }}
70
+ />
63
71
  <Modal
64
- title={t('DataSource.dialog.create_data_source')}
65
- onClose={() => { commonStore.setShowDSPanel(false) }}
66
- >
67
- <DataSelection />
68
- {/* <DataSourcePanel /> */}
69
- </Modal>
70
- )}
71
- { preWorkDone && <CheckCircleIcon className="text-green-500 w-5 inline-block ml-2" /> }
72
- { !preWorkDone && <ArrowPathIcon className="text-yellow-500 w-5 inline-block ml-2" />}
73
- </Container>
74
- }
72
+ title={t("DataSource.dialog.create_data_source")}
73
+ onClose={() => {
74
+ commonStore.setShowDSPanel(false);
75
+ }}
76
+ show={showDSPanel}
77
+ >
78
+ <DataSelection />
79
+ </Modal>
80
+ {/* {showDSPanel && (
81
+ <Modal
82
+ title={t("DataSource.dialog.create_data_source")}
83
+ onClose={() => {
84
+ commonStore.setShowDSPanel(false);
85
+ }}
86
+ >
87
+ <DataSelection />
88
+ </Modal>
89
+ )} */}
90
+ {preWorkDone && <CheckCircleIcon className="text-green-500 w-5 inline-block ml-2" />}
91
+ {!preWorkDone && <ArrowPathIcon className="text-yellow-500 w-5 inline-block ml-2" />}
92
+ </Container>
93
+ );
94
+ };
75
95
 
76
- export default observer(DataSourceSegment);
96
+ export default observer(DataSourceSegment);
@@ -3,6 +3,7 @@ import { Droppable } from "@kanaries/react-beautiful-dnd";
3
3
  import { DRAGGABLE_STATE_KEYS } from './fieldsContext';
4
4
  import { AestheticFieldContainer } from './components'
5
5
  import OBFieldContainer from './obComponents/obFContainer';
6
+ import SingleEncodeEditor from './encodeFields/singleEncodeEditor';
6
7
 
7
8
  const aestheticFields = DRAGGABLE_STATE_KEYS.filter(f => ['color', 'opacity', 'size', 'shape'].includes(f.id));
8
9
 
@@ -12,7 +13,8 @@ const AestheticFields: React.FC = props => {
12
13
  aestheticFields.map(dkey => <AestheticFieldContainer name={dkey.id} key={dkey.id}>
13
14
  <Droppable droppableId={dkey.id} direction="horizontal">
14
15
  {(provided, snapshot) => (
15
- <OBFieldContainer dkey={dkey} provided={provided} />
16
+ // <OBFieldContainer dkey={dkey} provided={provided} />
17
+ <SingleEncodeEditor dkey={dkey} provided={provided} snapshot={snapshot} />
16
18
  )}
17
19
  </Droppable>
18
20
  </AestheticFieldContainer>)
@@ -5,18 +5,25 @@ import { COLORS } from "../config";
5
5
 
6
6
  export const AestheticSegment = styled.div`
7
7
  border: 1px solid #e5e7eb;
8
+ // dark mode
9
+ @media (prefers-color-scheme: dark) {
10
+ border: 1px solid #2d3748;
11
+ }
8
12
  font-size: 12px;
9
13
  margin: 0.2em;
10
14
 
11
15
  .aes-header{
12
16
  border-bottom: 1px solid #e5e7eb;
17
+ @media (prefers-color-scheme: dark) {
18
+ border-bottom: 1px solid #2d3748;
19
+ }
13
20
  padding: 0.6em;
14
21
  h4 {
15
22
  font-weight: 400;
16
23
  }
17
24
  }
18
25
  .aes-container{
19
- overflow-x: auto;
26
+ /* overflow-x: auto; */
20
27
  }
21
28
 
22
29
  `
@@ -85,12 +92,18 @@ export const FilterFieldsContainer = styled.div({
85
92
  export const FieldListSegment = styled.div`
86
93
  display: flex;
87
94
  border: 1px solid #e5e7eb;
95
+ @media (prefers-color-scheme: dark) {
96
+ border: 1px solid #2d3748;
97
+ }
88
98
  margin: 0.2em;
89
99
  font-size: 12px;
90
100
  div.fl-header {
91
101
  /* flex-basis: 100px; */
92
102
  width: 100px;
93
103
  border-right: 1px solid #e5e7eb;
104
+ @media (prefers-color-scheme: dark) {
105
+ border-right: 1px solid #2d3748;
106
+ }
94
107
  flex-shrink: 0;
95
108
  h4 {
96
109
  margin: 0.6em;
@@ -108,11 +121,17 @@ export const FieldListSegment = styled.div`
108
121
 
109
122
  export const FilterFieldSegment = styled.div`
110
123
  border: 1px solid #e5e7eb;
124
+ @media (prefers-color-scheme: dark) {
125
+ border: 1px solid #2d3748;
126
+ }
111
127
  font-size: 12px;
112
128
  margin: 0.2em;
113
129
 
114
130
  .flt-header {
115
131
  border-bottom: 1px solid #e5e7eb;
132
+ @media (prefers-color-scheme: dark) {
133
+ border-bottom: 1px solid #2d3748;
134
+ }
116
135
  padding: 0.6em;
117
136
 
118
137
  > h4 {
@@ -144,7 +163,7 @@ export const Pill = styled.div<{colType: 'discrete' | 'continuous'}>`
144
163
  font-size: 12px;
145
164
  height: 20px;
146
165
  min-width: 150px;
147
- overflow-y: hidden;
166
+ /* overflow-y: hidden; */
148
167
  padding: 0 10px;
149
168
  user-select: none;
150
169
  text-overflow: ellipsis;
@@ -1,46 +1,54 @@
1
- import React from 'react';
2
- import { Draggable, DroppableProvided } from '@kanaries/react-beautiful-dnd';
3
- import { observer } from 'mobx-react-lite';
4
- import { useGlobalStore } from '../../store';
5
- import DataTypeIcon from '../../components/dataTypeIcon';
6
- import { FieldPill } from './fieldPill';
1
+ import React from "react";
2
+ import { Draggable, DroppableProvided } from "@kanaries/react-beautiful-dnd";
3
+ import { observer } from "mobx-react-lite";
4
+ import { useGlobalStore } from "../../store";
5
+ import DataTypeIcon from "../../components/dataTypeIcon";
6
+ import { FieldPill } from "./fieldPill";
7
7
 
8
8
  interface Props {
9
9
  provided: DroppableProvided;
10
10
  }
11
- const DimFields: React.FC<Props> = props => {
11
+ const DimFields: React.FC<Props> = (props) => {
12
12
  const { provided } = props;
13
13
  const { vizStore } = useGlobalStore();
14
14
  const dimensions = vizStore.draggableFieldState.dimensions;
15
- return <div {...provided.droppableProps} ref={provided.innerRef}>
16
- {dimensions.map((f, index) => (
17
- <Draggable key={f.dragId} draggableId={f.dragId} index={index}>
18
- {(provided, snapshot) => {
19
-
20
- return (
21
- <>
22
- <FieldPill
23
- className="pt-0.5 pb-0.5 pl-2 pr-2 mx-0 m-1 text-xs hover:bg-blue-100 rounded-full truncate border border-transparent"
24
- ref={provided.innerRef}
25
- isDragging={snapshot.isDragging}
26
- {...provided.draggableProps}
27
- {...provided.dragHandleProps}
28
- >
29
- <DataTypeIcon dataType={f.semanticType} analyticType={f.analyticType} /> {f.name}&nbsp;
30
- </FieldPill>
31
- {
32
- <FieldPill className={`pt-0.5 pb-0.5 pl-2 pr-2 mx-0 m-1 text-xs hover:bg-blue-100 rounded-full border-blue-400 border truncate ${snapshot.isDragging ? '' : 'hidden'}`}
33
- isDragging={snapshot.isDragging}
15
+ return (
16
+ <div {...provided.droppableProps} ref={provided.innerRef}>
17
+ {dimensions.map((f, index) => (
18
+ <Draggable key={f.dragId} draggableId={f.dragId} index={index}>
19
+ {(provided, snapshot) => {
20
+ return (
21
+ <>
22
+ <FieldPill
23
+ className={`dark:text-white pt-0.5 pb-0.5 pl-2 pr-2 mx-0 m-1 text-xs hover:bg-blue-100 dark:hover:bg-blue-800 rounded-full truncate border border-transparent ${
24
+ snapshot.isDragging ? "bg-blue-100 dark:bg-blue-800" : ""
25
+ }`}
26
+ ref={provided.innerRef}
27
+ isDragging={snapshot.isDragging}
28
+ {...provided.draggableProps}
29
+ {...provided.dragHandleProps}
34
30
  >
35
- <DataTypeIcon dataType={f.semanticType} analyticType={f.analyticType} /> {f.name}&nbsp;
31
+ <DataTypeIcon dataType={f.semanticType} analyticType={f.analyticType} /> {f.name}
32
+ &nbsp;
36
33
  </FieldPill>
37
- }
38
- </>
39
- );
40
- }}
41
- </Draggable>
42
- ))}
43
- </div>
44
- }
34
+ {
35
+ <FieldPill
36
+ className={`dark:text-white pt-0.5 pb-0.5 pl-2 pr-2 mx-0 m-1 text-xs hover:bg-blue-100 dark:hover:bg-blue-800 rounded-full border-blue-400 border truncate ${
37
+ snapshot.isDragging ? "bg-blue-100 dark:bg-blue-800" : "hidden"
38
+ }`}
39
+ isDragging={snapshot.isDragging}
40
+ >
41
+ <DataTypeIcon dataType={f.semanticType} analyticType={f.analyticType} />{" "}
42
+ {f.name}&nbsp;
43
+ </FieldPill>
44
+ }
45
+ </>
46
+ );
47
+ }}
48
+ </Draggable>
49
+ ))}
50
+ </div>
51
+ );
52
+ };
45
53
 
46
54
  export default observer(DimFields);
@@ -16,14 +16,14 @@ const DatasetFields: React.FC = (props) => {
16
16
  const { t } = useTranslation("translation", { keyPrefix: "main.tabpanel.DatasetFields" });
17
17
 
18
18
  return (
19
- <DSContainer className="border-gray-200 flex md:flex-col" style={{ paddingBlock: 0, paddingInline: '0.6em' }}>
19
+ <DSContainer className="border-gray-200 dark:border-gray-700 flex md:flex-col" style={{ paddingBlock: 0, paddingInline: '0.6em' }}>
20
20
  <h4 className="text-xs mb-2 flex-grow-0 cursor-default select-none mt-2">{t("field_list")}</h4>
21
21
  <div className="pd-1 overflow-y-auto" style={{ maxHeight: "380px", minHeight: '100px' }}>
22
22
  <Droppable droppableId="dimensions" direction="vertical">
23
23
  {(provided, snapshot) => <DimFields provided={provided} />}
24
24
  </Droppable>
25
25
  </div>
26
- <div className="border-t flex-grow pd-1 overflow-y-auto">
26
+ <div className="border-t dark:border-gray-800 flex-grow pd-1 overflow-y-auto">
27
27
  <Droppable droppableId="measures" direction="vertical">
28
28
  {(provided, snapshot) => <MeaFields provided={provided} />}
29
29
  </Droppable>
@@ -1,58 +1,84 @@
1
- import React from 'react';
2
- import { Draggable, DroppableProvided } from '@kanaries/react-beautiful-dnd';
3
- import { observer } from 'mobx-react-lite';
4
- import { useGlobalStore } from '../../store';
5
- import DataTypeIcon from '../../components/dataTypeIcon';
6
- import { FieldPill } from './fieldPill';
1
+ import React, { useCallback, useMemo } from "react";
2
+ import { Draggable, DroppableProvided } from "@kanaries/react-beautiful-dnd";
3
+ import { observer } from "mobx-react-lite";
4
+ import { useGlobalStore } from "../../store";
5
+ import DataTypeIcon from "../../components/dataTypeIcon";
6
+ import { FieldPill } from "./fieldPill";
7
+ import DropdownContext, { IDropdownContextOption } from "../../components/dropdownContext";
7
8
 
8
9
  interface Props {
9
10
  provided: DroppableProvided;
10
11
  }
11
- const MeaFields: React.FC<Props> = props => {
12
+ const MeaFields: React.FC<Props> = (props) => {
12
13
  const { provided } = props;
13
14
  const { vizStore } = useGlobalStore();
14
15
  const measures = vizStore.draggableFieldState.measures;
15
- return <div {...provided.droppableProps} ref={provided.innerRef}>
16
- {measures.map((f, index) => (
17
- <Draggable key={f.dragId} draggableId={f.dragId} index={index}>
18
- {(provided, snapshot) => {
19
- return (
20
- <>
21
- <FieldPill
22
- className="pt-0.5 pb-0.5 pl-2 pr-2 mx-0 m-1 text-xs hover:bg-blue-100 rounded-full truncate border border-transparent"
23
- isDragging={snapshot.isDragging}
24
- ref={provided.innerRef}
25
- {...provided.draggableProps}
26
- {...provided.dragHandleProps}
27
- >
28
- <DataTypeIcon dataType={f.semanticType} analyticType={f.analyticType} /> {f.name}&nbsp;
16
+
17
+ const MEA_ACTION_OPTIONS = useMemo<IDropdownContextOption[]>(() => {
18
+ return [
19
+ {
20
+ value: "bin",
21
+ label: "Bin",
22
+ },
23
+ {
24
+ value: "log10",
25
+ label: "Log10",
26
+ },
27
+ ];
28
+ }, []);
29
+
30
+ const fieldActionHandler = useCallback((selectedValue: any, opIndex: number, meaIndex: number) => {
31
+ if (selectedValue === "bin") {
32
+ vizStore.createBinField("measures", meaIndex);
33
+ } else if (selectedValue === "log10") {
34
+ vizStore.createLogField("measures", meaIndex);
35
+ }
36
+ }, []);
37
+ return (
38
+ <div {...provided.droppableProps} ref={provided.innerRef}>
39
+ {measures.map((f, index) => (
40
+ <Draggable key={f.dragId} draggableId={f.dragId} index={index}>
41
+ {(provided, snapshot) => {
42
+ return (
43
+ <div className="block">
44
+ <DropdownContext
45
+ disable={snapshot.isDragging}
46
+ options={MEA_ACTION_OPTIONS}
47
+ onSelect={(v, opIndex) => {
48
+ fieldActionHandler(v, opIndex, index);
49
+ }}
50
+ >
51
+ <FieldPill
52
+ className={`dark:text-white pt-0.5 pb-0.5 pl-2 pr-2 mx-0 m-1 text-xs hover:bg-purple-100 dark:hover:bg-purple-800 rounded-full truncate border border-transparent ${
53
+ snapshot.isDragging ? "bg-purple-100 dark:bg-purple-800" : ""
54
+ }`}
55
+ isDragging={snapshot.isDragging}
56
+ ref={provided.innerRef}
57
+ {...provided.draggableProps}
58
+ {...provided.dragHandleProps}
59
+ >
60
+ <DataTypeIcon dataType={f.semanticType} analyticType={f.analyticType} />{" "}
61
+ {f.name}&nbsp;
62
+ </FieldPill>
63
+ </DropdownContext>
29
64
  {
30
- f.fid && !snapshot.isDragging && <select className="bg-transparent text-gray-700 float-right focus:outline-none focus:border-gray-500" value="" onChange={e => {
31
- if (e.target.value === 'bin') {
32
- vizStore.createBinField('measures', index)
33
- } else if (e.target.value === 'log10') {
34
- vizStore.createLogField('measures', index)
35
- }
36
- }}>
37
- <option value=""></option>
38
- <option value="bin">bin</option>
39
- <option value="log10">log10</option>
40
- </select>
65
+ <FieldPill
66
+ className={`dark:text-white pt-0.5 pb-0.5 pl-2 pr-2 mx-0 m-1 text-xs hover:bg-purple-100 dark:hover:bg-purple-800 rounded-full border-purple-400 border truncate ${
67
+ snapshot.isDragging ? "bg-purple-100 dark:bg-purple-800" : "hidden"
68
+ }`}
69
+ isDragging={snapshot.isDragging}
70
+ >
71
+ <DataTypeIcon dataType={f.semanticType} analyticType={f.analyticType} />{" "}
72
+ {f.name}&nbsp;
73
+ </FieldPill>
41
74
  }
42
- </FieldPill>
43
- {
44
- <FieldPill className={`pt-0.5 pb-0.5 pl-2 pr-2 mx-0 m-1 text-xs hover:bg-blue-100 rounded-full border-blue-400 border truncate ${snapshot.isDragging ? '' : 'hidden'}`}
45
- isDragging={snapshot.isDragging}
46
- >
47
- <DataTypeIcon dataType={f.semanticType} analyticType={f.analyticType} /> {f.name}&nbsp;
48
- </FieldPill>
49
- }
50
- </>
51
- );
52
- }}
53
- </Draggable>
54
- ))}
55
- </div>
56
- }
75
+ </div>
76
+ );
77
+ }}
78
+ </Draggable>
79
+ ))}
80
+ </div>
81
+ );
82
+ };
57
83
 
58
84
  export default observer(MeaFields);
@@ -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 = "flex truncate";
32
+ let btnComputedClassName = "grow shrink 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 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">{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-8 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-amber-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">
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;