Skip to content

Commit

Permalink
Merge pull request #132 from AntoineYANG/feat-g_walker-filter-yzd-0919
Browse files Browse the repository at this point in the history
Feat g walker filter yzd 0919
  • Loading branch information
ObservedObserver authored Sep 21, 2022
2 parents ef2451e + 2e8176c commit f8a81ec
Show file tree
Hide file tree
Showing 19 changed files with 1,322 additions and 36 deletions.
2 changes: 2 additions & 0 deletions packages/graphic-walker/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import VisNav from './segments/visNav';
import { useTranslation } from 'react-i18next';
import { mergeLocaleRes, setLocaleLanguage } from './locales/i18n';
import Menubar from './visualSettings/menubar';
import FilterField from './fields/filterField';


export interface EditorProps {
Expand Down Expand Up @@ -103,6 +104,7 @@ const App: React.FC<EditorProps> = props => {
<DatasetFields />
</div>
<div className="col-span-2 xl:col-span-1">
<FilterField />
<AestheticFields />
</div>
<div className="col-span-7 xl:col-span-4">
Expand Down
22 changes: 18 additions & 4 deletions packages/graphic-walker/src/components/modal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useRef } from 'react';
import styled from 'styled-components';
import { XCircleIcon } from '@heroicons/react/24/outline';

Expand All @@ -9,7 +9,7 @@ const Background = styled.div({
top: 0,
width: '100vw',
height: '100vh',
backdropFilter: 'blur(2px)',
backdropFilter: 'blur(1px)',
zIndex: 25535,
});

Expand Down Expand Up @@ -42,13 +42,27 @@ interface ModalProps {
}
const Modal: React.FC<ModalProps> = props => {
const { onClose, title } = props;
const prevMouseDownTimeRef = useRef(0);

return (
<Background onClick={onClose}>
<Background
// This is a safer replacement of onClick handler.
// onClick also happens if the click event is begun by pressing mouse button
// at a different element and then released when the mouse is moved on the target element.
// This case is required to be prevented, especially disturbing when interacting
// with a Slider component.
onMouseDown={() => prevMouseDownTimeRef.current = Date.now()}
onMouseOut={() => prevMouseDownTimeRef.current = 0}
onMouseUp={() => {
if (Date.now() - prevMouseDownTimeRef.current < 1000) {
onClose?.();
}
}}
>
<Container
role="dialog"
className="shadow-lg"
onClick={e => e.stopPropagation()}
onMouseDown={e => e.stopPropagation()}
>
<div className="header relative h-9">
<header className="font-bold">
Expand Down
4 changes: 2 additions & 2 deletions packages/graphic-walker/src/dataSource/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ const DataSourceSegment: React.FC<DSSegmentProps> = props => {

const { currentDataset, datasets, showDSPanel } = commonStore;

return <Container>
return <Container className="flex flex-row items-stretch">
{!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>}
<label className="text-xs mr-1">
<label className="text-xs mr-1 whitespace-nowrap self-center h-4">
{t('DataSource.labels.cur_dataset')}
</label>
<select
Expand Down
44 changes: 44 additions & 0 deletions packages/graphic-walker/src/fields/components.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,19 @@ export const AestheticFieldContainer: React.FC<{ name: string }> = props => {
);
}

export const FilterFieldContainer: React.FC = props => {
const { t } = useTranslation('translation', { keyPrefix: 'constant.draggable_key' });

return (
<FilterFieldSegment>
<div className="flt-header cursor-default select-none">
<h4>{t('filters')}</h4>
</div>
<div className="flt-container">{props.children}</div>
</FilterFieldSegment>
);
}

export const FieldsContainer = styled.div`
display: flex;
padding: 0.2em;
Expand All @@ -58,6 +71,18 @@ export const FieldsContainer = styled.div`
}
`;

export const FilterFieldsContainer = styled.div({
display: 'flex',
flexDirection: 'column',
paddingBlock: '0.5em 0.8em',
paddingInline: '0.2em',
minHeight: '4em',
'> div': {
marginBlock: '0.3em',
marginInline: '1px',
},
});

export const FieldListSegment = styled.div`
display: flex;
border: 1px solid #dfe3e8;
Expand All @@ -82,6 +107,25 @@ export const FieldListSegment = styled.div`
}
`;

export const FilterFieldSegment = styled.div({
border: '1px solid #dfe3e8',
fontSize: '12px',
margin: '0.2em',

'.flt-header': {
borderBottom: '1px solid #dfe3e8',
padding: '0.6em',

'> h4': {
fontWeight: 400,
},
},

'.flt-container': {

},
});

export const Pill = styled.div<{colType: 'discrete' | 'continuous'}>`
background-color: ${props => props.colType === 'continuous' ? COLORS.white : COLORS.black};
border-color: ${props => props.colType === 'continuous' ? COLORS.black : COLORS.white};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ import styled from 'styled-components';
*/
export const FieldPill = styled.div<{isDragging: boolean}>`
transform: ${props => !props.isDragging && 'translate(0px, 0px) !important'};
user-select: none;
`
3 changes: 2 additions & 1 deletion packages/graphic-walker/src/fields/fieldsContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ export const DRAGGABLE_STATE_KEYS: Readonly<IDraggableStateKey[]> = [
{ id: 'size', mode: 1 },
{ id: 'shape', mode: 1},
{ id: 'theta', mode: 1 },
{ id: 'radius', mode: 1 }
{ id: 'radius', mode: 1 },
{ id: 'filters', mode: 1 },
] as const;

export const AGGREGATOR_LIST: Readonly<string[]> = [
Expand Down
143 changes: 143 additions & 0 deletions packages/graphic-walker/src/fields/filterField/filterEditDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { CheckCircleIcon } from '@heroicons/react/24/outline';
import { observer } from 'mobx-react-lite';
import React from 'react';
import { useTranslation } from 'react-i18next';

import Modal from '../../components/modal';
import type { IFilterField, IFilterRule } from '../../interfaces';
import { useGlobalStore } from '../../store';
import Tabs, { RuleFormProps } from './tabs';


const QuantitativeRuleForm: React.FC<RuleFormProps> = ({
field,
onChange,
}) => {
return (
<Tabs
field={field}
onChange={onChange}
tabs={['range', 'one of']}
/>
);
};

const NominalRuleForm: React.FC<RuleFormProps> = ({
field,
onChange,
}) => {
return (
<Tabs
field={field}
onChange={onChange}
tabs={['one of']}
/>
);
};

const OrdinalRuleForm: React.FC<RuleFormProps> = ({
field,
onChange,
}) => {
return (
<Tabs
field={field}
onChange={onChange}
tabs={['range', 'one of']}
/>
);
};

const TemporalRuleForm: React.FC<RuleFormProps> = ({
field,
onChange,
}) => {
return (
<Tabs
field={field}
onChange={onChange}
tabs={['one of', 'temporal range']}
/>
);
};

const EmptyForm: React.FC<RuleFormProps> = () => <React.Fragment />;

const FilterEditDialog: React.FC = observer(() => {
const { vizStore } = useGlobalStore();
const { editingFilterIdx, draggableFieldState } = vizStore;

const { t } = useTranslation('translation', { keyPrefix: 'filters' });

const field = React.useMemo(() => {
return editingFilterIdx !== null ? draggableFieldState.filters[editingFilterIdx] : null;
}, [editingFilterIdx, draggableFieldState]);

const [uncontrolledField, setUncontrolledField] = React.useState(field as IFilterField | null);
const ufRef = React.useRef(uncontrolledField);
ufRef.current = uncontrolledField;

React.useEffect(() => {
if (field !== ufRef.current) {
setUncontrolledField(field);
}
}, [field]);

const handleChange = React.useCallback((r: IFilterRule) => {
if (editingFilterIdx !== null) {
setUncontrolledField(uf => ({
...uf,
rule: r,
}) as IFilterField);
}
}, [editingFilterIdx]);

const handleSubmit = React.useCallback(() => {
if (editingFilterIdx !== null) {
vizStore.writeFilter(editingFilterIdx, uncontrolledField?.rule ?? null);
}

vizStore.closeFilterEditing();
}, [editingFilterIdx, uncontrolledField]);

const Form = field ? ({
quantitative: QuantitativeRuleForm,
nominal: NominalRuleForm,
ordinal: OrdinalRuleForm,
temporal: TemporalRuleForm,
}[field.semanticType] as React.FC<RuleFormProps>) : EmptyForm;

return uncontrolledField ? (
<Modal
title={t('editing')}
onClose={() => vizStore.closeFilterEditing()}
>
<header className="text-lg font-semibold py-2 outline-none">
{t('form.name')}
</header>
<input className="border py-1 px-4" readOnly value={uncontrolledField.name}/>
<header className="text-lg font-semibold py-2 outline-none">
{t('form.rule')}
</header>
<Form
field={uncontrolledField}
onChange={handleChange}
/>
<div className="flex justify-center text-green-500 mt-4">
<CheckCircleIcon
width="3em"
height="3em"
role="button"
tabIndex={0}
aria-label="ok"
className="cursor-pointer hover:bg-green-50 p-1"
onClick={handleSubmit}
strokeWidth="1.5"
/>
</div>
</Modal>
) : null;
});


export default FilterEditDialog;
Loading

0 comments on commit f8a81ec

Please sign in to comment.