Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Importing list of values to String filter menu #419

Merged
merged 15 commits into from
May 28, 2019
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,92 +19,12 @@

.string-filter-menu {

.side-by-side {
@extend %side-by-side;

:not(:last-child) {
margin-right: 0;
}
.string-filter-content {
display: flex;
flex-wrap: wrap;
}

.search-box {
height: 30px;
margin-left: 6px;
width: 100%;

.clearable-input {
@extend %input-cont-inset;
width: 100%;
height: $input-height;
padding-left: 6px;
}
}

.menu-table {

@extend %white-shadow;
background: $white;
color: $text-standard;

&::after {
@include unpin-full(0, -14px, 0, -14px);
}

.rows {
height: 280px;
margin-top: 10px;
margin-left: -$padding-compact;
margin-right: -$padding-compact;

overflow: auto;
}

.row {
height: 24px;
cursor: pointer;
padding: 0 $padding-compact;

.row-wrapper {
@include ellipsis;


height: 100%;

.label {
display: inline-block;
vertical-align: top;
padding-top: 4px;
}
}

&:hover {
background: $hover;
}

&:last-child {
margin-bottom: 12px;
}

&.no-select {
pointer-events: none;
user-select: none;
}
}

.message {
padding: 4px 10px;
color: $text-light;
font-style: italic;
}

.matching-values-message {
padding: 4px 10px;
color: $text-light;
}

.loader,
.message {
@include pin-full;
}
.filter-options-dropdown {
margin-right: 6px;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,12 @@ import { Clicker } from "../../../../common/models/clicker/clicker";
import { Dimension } from "../../../../common/models/dimension/dimension";
import { DragPosition } from "../../../../common/models/drag-position/drag-position";
import { Essence } from "../../../../common/models/essence/essence";
import { FilterClause, StringFilterAction, StringFilterClause } from "../../../../common/models/filter-clause/filter-clause";
import { FilterClause } from "../../../../common/models/filter-clause/filter-clause";
import { Filter, FilterMode } from "../../../../common/models/filter/filter";
import { Stage } from "../../../../common/models/stage/stage";
import { Timekeeper } from "../../../../common/models/timekeeper/timekeeper";
import { Fn } from "../../../../common/utils/general/general";
import { BubbleMenu } from "../../bubble-menu/bubble-menu";
import { ClearableInput } from "../../clearable-input/clearable-input";
import { FilterOption, FilterOptionsDropdown } from "../../filter-options-dropdown/filter-options-dropdown";
import { PreviewStringFilterMenu } from "../../preview-string-filter-menu/preview-string-filter-menu";
import { SelectableStringFilterMenu } from "../../selectable-string-filter-menu/selectable-string-filter-menu";
Expand All @@ -46,47 +45,19 @@ export interface StringFilterMenuProps {

export interface StringFilterMenuState {
filterMode?: FilterMode;
searchText?: string;
}

export class StringFilterMenu extends React.Component<StringFilterMenuProps, StringFilterMenuState> {
public mounted: boolean;

constructor(props: StringFilterMenuProps) {
super(props);
this.state = {
filterMode: null,
searchText: ""
};
}

componentWillMount() {
const { essence: { colors, filter }, dimension } = this.props;

private initialFilterMode = (): FilterMode => {
const { essence: { filter }, dimension } = this.props;
const filterMode = filter.getModeForDimension(dimension);
if (filterMode && !this.state.filterMode) {
const searchText = this.getInitialSearchText();
this.setState({ filterMode, searchText });
} else if (colors) {
this.setState({ filterMode: FilterMode.INCLUDE });
}
return filterMode || FilterMode.INCLUDE;
}

getInitialSearchText(): string {
const { essence, dimension } = this.props;
const filterClause = essence.filter.getClauseForDimension(dimension);
if (!(filterClause instanceof StringFilterClause)) throw new Error(`Expected StringFilterClause. Got ${filterClause}`);
if (filterClause.action === StringFilterAction.IN) return "";
return filterClause.values.first();
}
state: StringFilterMenuState = { filterMode: this.initialFilterMode() };

onSelectFilterOption = (filterMode: FilterMode) => {
this.setState({ filterMode });
}

updateSearchText = (searchText: string) => {
this.setState({ searchText });
}
onSelectFilterOption = (filterMode: FilterMode) => this.setState({ filterMode });

updateFilter: (clause: FilterClause) => Filter = clause => {
const { essence, dimension, changePosition } = this.props;
Expand Down Expand Up @@ -114,73 +85,45 @@ export class StringFilterMenu extends React.Component<StringFilterMenuProps, Str
return filterOptions;
}

renderMenuControls() {
const { filterMode, searchText } = this.state;

return <div className="string-filter-menu-controls">
<div className="side-by-side">
<FilterOptionsDropdown
selectedOption={filterMode}
onSelectOption={this.onSelectFilterOption}
filterOptions={this.getFilterOptions()}
/>
<div className="search-box">
<ClearableInput
placeholder="Search"
focusOnMount={true}
value={searchText}
onChange={this.updateSearchText}
/>
</div>
</div>
</div>;
renderFilterControls(): JSX.Element {
const { dimension, clicker, essence, timekeeper, onClose } = this.props;
const { filterMode } = this.state;
const onClauseChange = this.updateFilter;
const props = { dimension, clicker, essence, timekeeper, onClose, onClauseChange };

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why don't you add filterMode right here? then you could switch used component only.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TSX typing issues. If I add this there, props will have filterMode: FilterMode, and switch wouldn't narrow it's type inside object so types on component's won't match. If I narrow type via switch and then add to object, it has correct type.

switch (filterMode) {
case FilterMode.EXCLUDE:
case FilterMode.INCLUDE:
const selectableProps = { ...props, filterMode };
return <SelectableStringFilterMenu {...selectableProps} />;
case FilterMode.REGEX:
case FilterMode.CONTAINS:
const previewProps = { ...props, filterMode };
return <PreviewStringFilterMenu {...previewProps} />;
}
}

render() {
const { dimension, clicker, essence, timekeeper, onClose, containerStage, openOn, inside } = this.props;
const { filterMode, searchText } = this.state;
const { dimension, onClose, containerStage, openOn, inside } = this.props;
const { filterMode } = this.state;
if (!dimension) return null;

let menuSize: Stage = null;
let menuCont: JSX.Element = null;

if (filterMode === FilterMode.REGEX || filterMode === FilterMode.CONTAINS) {
menuSize = Stage.fromSize(350, 410);
menuCont = <PreviewStringFilterMenu
dimension={dimension}
clicker={clicker}
essence={essence}
timekeeper={timekeeper}
onClose={onClose}
searchText={searchText}
filterMode={filterMode}
onClauseChange={this.updateFilter}
/>;
} else {
menuSize = Stage.fromSize(250, 410);
menuCont = <SelectableStringFilterMenu
dimension={dimension}
clicker={clicker}
essence={essence}
timekeeper={timekeeper}
onClose={onClose}
searchText={searchText}
filterMode={filterMode}
onClauseChange={this.updateFilter}
/>;
}

return <BubbleMenu
className="string-filter-menu"
direction="down"
containerStage={containerStage}
stage={menuSize}
stage={Stage.fromSize(300, 410)}
openOn={openOn}
onClose={onClose}
inside={inside}
>
{this.renderMenuControls()}
{menuCont}
<div className="string-filter-content">
<FilterOptionsDropdown
selectedOption={filterMode}
onSelectOption={this.onSelectFilterOption}
filterOptions={this.getFilterOptions()}
/>
{this.renderFilterControls()}
</div>
</BubbleMenu>;
}
}
36 changes: 36 additions & 0 deletions src/client/components/paste-form/paste-form.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2017-2018 Allegro.pl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

@import '../../imports';

.paste-field {
@extend %default-input;
height: 280px;
font-size: 13px;
padding: 1px;
}

.paste-actions {
margin-top: 12px;

button {
min-width: 70px;

&:not(:last-child) {
margin-right: 6px;
}
}
}
60 changes: 60 additions & 0 deletions src/client/components/paste-form/paste-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2017-2018 Allegro.pl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Set } from "immutable";
import * as React from "react";
import { Unary } from "../../../common/utils/functional/functional";
import { Fn } from "../../../common/utils/general/general";
import { Button } from "../button/button";
import "./paste-form.scss";

interface PasteFormProps {
initialValues: Set<string>;
onSelect: Unary<Set<string>, void>;
onClose: Fn;
}

interface PasteFormState {
value: string;
}

export class PasteForm extends React.Component<PasteFormProps, PasteFormState> {

state: PasteFormState = { value: this.props.initialValues.join("\n") };

select = () => {
const { onClose, onSelect } = this.props;
const { value } = this.state;
onSelect(Set(value.split("\n")));
onClose();
}

cancel = () => this.props.onClose();

saveValue = ({ target: { value } }: React.ChangeEvent<HTMLTextAreaElement>) => this.setState({ value });

render() {
const { value } = this.state;
const {} = this.props;
return <div>
<textarea className="paste-field" value={value} onChange={this.saveValue} />
<div className="paste-actions">
<Button type="primary" title="Select" onClick={this.select} />
<Button type="secondary" title="Cancel" onClick={this.cancel} />
</div>
</div>;
}
}
Loading