Skip to content

Commit

Permalink
[feat] Adding applyLayerConfig action (#2337)
Browse files Browse the repository at this point in the history
  • Loading branch information
igorDykhta committed Sep 25, 2023
1 parent ae26de5 commit 85fa66f
Show file tree
Hide file tree
Showing 7 changed files with 422 additions and 47 deletions.
1 change: 1 addition & 0 deletions src/actions/src/action-types.ts
Expand Up @@ -60,6 +60,7 @@ export const ActionTypes = {
ADD_DATA: `${ACTION_PREFIX}ADD_DATA`,
ADD_FILTER: `${ACTION_PREFIX}ADD_FILTER`,
ADD_LAYER: `${ACTION_PREFIX}ADD_LAYER`,
APPLY_LAYER_CONFIG: `${ACTION_PREFIX}APPLY_LAYER_CONFIG`,
DUPLICATE_LAYER: `${ACTION_PREFIX}DUPLICATE_LAYER`,
INTERACTION_CONFIG_CHANGE: `${ACTION_PREFIX}INTERACTION_CONFIG_CHANGE`,
LAYER_CONFIG_CHANGE: `${ACTION_PREFIX}LAYER_CONFIG_CHANGE`,
Expand Down
31 changes: 30 additions & 1 deletion src/actions/src/vis-state-actions.ts
Expand Up @@ -35,11 +35,40 @@ import {
FeatureSelectionContext,
InteractionConfig,
Filter,
ParsedConfig
ParsedConfig,
ParsedLayer
} from '@kepler.gl/types';
// TODO - import LoaderObject type from @loaders.gl/core when supported
// TODO - import LoadOptions type from @loaders.gl/core when supported

export type ApplyLayerConfigUpdaterAction = {
oldLayerId: string;
newLayerConfig: ParsedLayer;
layerIndex?: number;
};

/**
* Update layer base config: dataId, label, column, isVisible
* @param oldLayerId - layer id to be updated
* @param newLayerConfig - new layer config
* @param layerIndex - (Optional) Index of the layer to be updated (can be useful in some cases, because
* the layer id might change during update, e.g. when the type of the layer changes)
* @returns action
* @public
*/
export function applyLayerConfig(
oldLayerId: string,
newLayerConfig: ParsedLayer,
layerIndex?: number
): Merge<ApplyLayerConfigUpdaterAction, {type: typeof ActionTypes.APPLY_LAYER_CONFIG}> {
return {
type: ActionTypes.APPLY_LAYER_CONFIG,
oldLayerId,
newLayerConfig,
layerIndex
};
}

export type LayerConfigChangeUpdaterAction = {
oldLayer: Layer;
newConfig: Partial<LayerBaseConfig>;
Expand Down
73 changes: 39 additions & 34 deletions src/components/src/side-panel/add-by-dataset-button.tsx
Expand Up @@ -29,6 +29,7 @@ import {Button, DatasetSquare} from '../common/styled-components';
import Typeahead from '../common/item-selector/typeahead';
import Accessor from '../common/item-selector/accessor';
import {useIntl} from 'react-intl';
import {RootContext} from '../context';

const DropdownContainer = styled.div.attrs({
className: 'add-layer-menu-dropdown'
Expand Down Expand Up @@ -159,40 +160,44 @@ const AddByDatasetButton: React.FC<AddByDatasetButtonProps> = ({
return options.length === 1 ? (
buttonRendered
) : (
<Tippy
trigger="click"
arrow={false}
interactive
placement="bottom"
appendTo="parent"
// @ts-ignore
onCreate={setTippyInstance}
duration={0}
content={
<DropdownMenu>
<DropdownContainer>
<Typeahead
className={TYPEAHEAD_CLASS}
customClasses={{
results: 'list-selector',
input: TYPEAHEAD_INPUT_CLASS,
listItem: 'list__item'
}}
placeholder={intl ? intl.formatMessage({id: 'placeholder.search'}) : 'Search'}
selectedItems={null}
options={options}
displayOption={Accessor.generateOptionToStringFor('label')}
filterOption={'label'}
searchable
onOptionSelected={onOptionSelected}
customListItemComponent={ListItem}
/>
</DropdownContainer>
</DropdownMenu>
}
>
{buttonRendered}
</Tippy>
<RootContext.Consumer>
{context => (
<Tippy
trigger="click"
arrow={false}
interactive
placement="bottom"
appendTo={context?.current || 'parent'}
// @ts-ignore
onCreate={setTippyInstance}
duration={0}
content={
<DropdownMenu>
<DropdownContainer>
<Typeahead
className={TYPEAHEAD_CLASS}
customClasses={{
results: 'list-selector',
input: TYPEAHEAD_INPUT_CLASS,
listItem: 'list__item'
}}
placeholder={intl ? intl.formatMessage({id: 'placeholder.search'}) : 'Search'}
selectedItems={null}
options={options}
displayOption={Accessor.generateOptionToStringFor('label')}
filterOption={'label'}
searchable
onOptionSelected={onOptionSelected}
customListItemComponent={ListItem}
/>
</DropdownContainer>
</DropdownMenu>
}
>
{buttonRendered}
</Tippy>
)}
</RootContext.Consumer>
);
};

Expand Down
71 changes: 61 additions & 10 deletions src/reducers/src/vis-state-merger.ts
Expand Up @@ -554,6 +554,7 @@ export function mergeAnimationConfig<S extends VisState>(
* @param fields
* @param savedCols
* @param emptyCols
* @param options
* @return - validated columns or null
*/

Expand All @@ -562,7 +563,8 @@ export function validateSavedLayerColumns(
savedCols: {
[key: string]: string;
} = {},
emptyCols: LayerColumns
emptyCols: LayerColumns,
options: {throwOnError?: boolean} = {}
) {
// Prepare columns for the validator
const columns: typeof emptyCols = {};
Expand All @@ -586,11 +588,21 @@ export function validateSavedLayerColumns(
validateColumn(columns[key], columns, fields)
);

if (allColFound) {
return columns;
const rv = allColFound ? columns : null;
if (options.throwOnError) {
const requiredColumns = Object.keys(emptyCols).filter(k => !emptyCols[k].optional);
const missingColumns = requiredColumns.filter(k => !columns?.[k].value);
if (missingColumns.length) {
throw new Error(`Layer has missing or invalid columns: ${missingColumns.join(', ')}`);
}
const configColumns = Object.keys(savedCols);
const invalidColumns = configColumns.filter(k => !columns?.[k]?.value);
if (invalidColumns.length) {
throw new Error(`Layer has invalid columns: ${invalidColumns.join(', ')}`);
}
}

return null;
return rv;
}

export function validateColumn(
Expand All @@ -613,9 +625,15 @@ export function validateColumn(
*
* @param {Array<Object>} fields
* @param {Object} savedTextLabel
* @param {Object} options
* @return {Object} - validated textlabel
*/
export function validateSavedTextLabel(fields, [layerTextLabel], savedTextLabel) {
export function validateSavedTextLabel(
fields,
[layerTextLabel],
savedTextLabel,
options: {throwOnError?: boolean} = {}
) {
const savedTextLabels = Array.isArray(savedTextLabel) ? savedTextLabel : [savedTextLabel];

// validate field
Expand All @@ -626,6 +644,10 @@ export function validateSavedTextLabel(fields, [layerTextLabel], savedTextLabel)
)
: null;

if (field === undefined && options.throwOnError) {
throw new Error(`Layer has invalid text label field: ${JSON.stringify(textLabel.field)}`);
}

return Object.keys(layerTextLabel).reduce(
(accu, key) => ({
...accu,
Expand All @@ -643,7 +665,8 @@ export function validateSavedTextLabel(fields, [layerTextLabel], savedTextLabel)
export function validateSavedVisualChannels(
fields: KeplerTable['fields'],
newLayer: Layer,
savedLayer: ParsedLayer
savedLayer: ParsedLayer,
options: {throwOnError?: boolean} = {}
): null | Layer {
Object.values(newLayer.visualChannels).forEach(({field, scale, key}) => {
let foundField;
Expand All @@ -663,13 +686,20 @@ export function validateSavedVisualChannels(
}

newLayer.validateVisualChannel(key);
if (options.throwOnError) {
const fieldName = savedLayer.config?.[field]?.name;
if (fieldName && fieldName !== newLayer.config[field]?.name) {
throw new Error(`Layer has invalid visual channel field: ${field}`);
}
}
}
});
return newLayer;
}

type ValidateLayerOption = {
allowEmptyColumn?: boolean;
throwOnError?: boolean;
};

export function validateLayersByDatasets(
Expand Down Expand Up @@ -710,15 +740,20 @@ export function validateLayersByDatasets(
* Validate saved layer config with new data,
* update fieldIdx based on new fields
*/
// eslint-disable-next-line complexity
export function validateLayerWithData(
{fields, id: dataId}: KeplerTable,
savedLayer: ParsedLayer,
layerClasses: VisState['layerClasses'],
options: ValidateLayerOption = {}
): Layer | null {
const {type} = savedLayer;
const {throwOnError} = options;
// layer doesnt have a valid type
if (!type || !layerClasses.hasOwnProperty(type) || !savedLayer.config) {
if (throwOnError) {
throw new Error(`Layer has invalid type "${type}" or config is missing`);
}
return null;
}

Expand All @@ -734,8 +769,13 @@ export function validateLayerWithData(

// find column fieldIdx
const columnConfig = newLayer.getLayerColumns();
if (Object.keys(columnConfig).length) {
const columns = validateSavedLayerColumns(fields, savedLayer.config.columns, columnConfig);
if (Object.keys(columnConfig)) {
const columns = validateSavedLayerColumns(
fields,
savedLayer.config.columns,
columnConfig,
options
);
if (columns) {
newLayer.updateLayerConfig({columns});
} else if (!options.allowEmptyColumn) {
Expand All @@ -746,11 +786,16 @@ export function validateLayerWithData(
// visual channel field is saved to be {name, type}
// find visual channel field by matching both name and type
// refer to vis-state-schema.js VisualChannelSchemaV1
newLayer = validateSavedVisualChannels(fields, newLayer, savedLayer);
newLayer = validateSavedVisualChannels(fields, newLayer, savedLayer, options);

const textLabel =
savedLayer.config.textLabel && newLayer.config.textLabel
? validateSavedTextLabel(fields, newLayer.config.textLabel, savedLayer.config.textLabel)
? validateSavedTextLabel(
fields,
newLayer.config.textLabel,
savedLayer.config.textLabel,
options
)
: newLayer.config.textLabel;

// copy visConfig over to emptyLayer to make sure it has all the props
Expand All @@ -765,6 +810,12 @@ export function validateLayerWithData(
textLabel
});

if (throwOnError) {
if (!newLayer.isValidToSave()) {
throw new Error(`Layer is not valid to save: ${newLayer.id}`);
}
}

return newLayer;
}

Expand Down

0 comments on commit 85fa66f

Please sign in to comment.