1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-25 04:46:40 +07:00

removed tag reducer

This commit is contained in:
Fedor Katurov 2022-01-03 13:15:17 +07:00
parent 11b39b8766
commit 31e433af3e
17 changed files with 41 additions and 192 deletions

View file

@ -1,6 +1,6 @@
import React, { FC, memo } from 'react';
import { ITag } from '~/redux/types';
import { Tags } from '~/components/tags/Tags';
import { Tags } from '~/containers/tags/Tags';
interface IProps {
is_deletable?: boolean;

View file

@ -1,6 +1,6 @@
import React, { FC, memo } from 'react';
import { ITag } from '~/redux/types';
import { Tags } from '~/components/tags/Tags';
import { Tags } from '~/containers/tags/Tags';
interface IProps {
is_editable?: boolean;

View file

@ -1,34 +1,23 @@
import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useRef, useState, VFC } from 'react';
import styles from './styles.module.scss';
import classNames from 'classnames';
import { connect } from 'react-redux';
import * as TAG_ACTIONS from '~/redux/tag/actions';
import { selectTagAutocomplete } from '~/redux/tag/selectors';
import { separateTagOptions } from '~/utils/tag';
import { TagAutocompleteRow } from '~/components/tags/TagAutocompleteRow';
import { usePopper } from 'react-popper';
const mapStateToProps = selectTagAutocomplete;
const mapDispatchToProps = {
tagSetAutocomplete: TAG_ACTIONS.tagSetAutocomplete,
tagLoadAutocomplete: TAG_ACTIONS.tagLoadAutocomplete,
};
interface TagAutocompleteProps {
exclude: string[];
input: HTMLInputElement;
onSelect: (val: string) => void;
search: string;
options: string[];
}
type Props = ReturnType<typeof mapStateToProps> &
typeof mapDispatchToProps & {
exclude: string[];
input: HTMLInputElement;
onSelect: (val: string) => void;
search: string;
};
const TagAutocompleteUnconnected: FC<Props> = ({
const TagAutocomplete: VFC<TagAutocompleteProps> = ({
exclude,
input,
onSelect,
search,
tagSetAutocomplete,
tagLoadAutocomplete,
options,
}) => {
const [selected, setSelected] = useState(-1);
@ -80,19 +69,6 @@ const TagAutocompleteUnconnected: FC<Props> = ({
return () => input.removeEventListener('keydown', onKeyDown);
}, [input, onKeyDown]);
useEffect(() => {
setSelected(-1);
tagLoadAutocomplete(search, exclude);
}, [exclude, search, tagLoadAutocomplete]);
useEffect(() => {
tagSetAutocomplete({ options: [] });
return () => {
tagSetAutocomplete({ options: [] });
};
}, [tagSetAutocomplete]);
useEffect(() => {
if (!scroll.current || !scroll.current?.children[selected + 1]) return;
const el = scroll.current?.children[selected + 1] as HTMLDivElement;
@ -143,6 +119,4 @@ const TagAutocompleteUnconnected: FC<Props> = ({
);
};
const TagAutocomplete = connect(mapStateToProps, mapDispatchToProps)(TagAutocompleteUnconnected);
export { TagAutocomplete };

View file

@ -1,145 +0,0 @@
import React, { ChangeEvent, FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { TagAutocomplete } from '~/components/tags/TagAutocomplete';
import { TagWrapper } from '~/components/tags/TagWrapper';
import styles from './styles.module.scss';
const placeholder = 'Добавить';
const prepareInput = (input: string): string[] => {
return input
.split(',')
.map((title: string) =>
title
.trim()
.substr(0, 32)
.toLowerCase()
)
.filter(el => el.length > 0);
};
interface IProps {
onAppend: (tags: string[]) => void;
onClearTag: () => string | undefined;
onSubmit: (last: string[]) => void;
exclude: string[];
}
const TagInput: FC<IProps> = ({ exclude, onAppend, onClearTag, onSubmit }) => {
const [focused, setFocused] = useState(false);
const [input, setInput] = useState('');
const ref = useRef<HTMLInputElement>(null);
const wrapper = useRef<HTMLDivElement>(null);
const onInput = useCallback(
({ target: { value } }: ChangeEvent<HTMLInputElement>) => {
if (!value.trim()) {
setInput(value || '');
return;
}
const items = prepareInput(value);
if (items.length > 1) {
onAppend(items.slice(0, items.length - 1));
}
setInput(items[items.length - 1] || '');
},
[onAppend]
);
const onKeyDown = useCallback(
({ key }) => {
if (key === 'Escape' && ref.current) {
setInput('');
ref.current.blur();
return;
}
if (key === 'Backspace' && input === '') {
setInput(onClearTag() || '');
return;
}
if (key === ',' || key === 'Comma') {
const created = prepareInput(input);
if (created.length) {
onAppend(created);
}
setInput('');
}
},
[input, setInput, onClearTag, onAppend]
);
const onFocus = useCallback(() => setFocused(true), []);
const onBlur = useCallback(
event => {
if (!wrapper.current || !ref.current) {
return;
}
if (wrapper.current.contains(event.target)) {
ref.current.focus();
return;
}
setFocused(false);
if (input.trim()) {
setInput('');
}
onSubmit([]);
},
[input, setInput, onSubmit]
);
const onAutocompleteSelect = useCallback(
(val: string) => {
onAppend([val]);
setInput('');
},
[onAppend, setInput]
);
const feature = useMemo(() => (input?.substr(0, 1) === '/' ? 'green' : ''), [input]);
useEffect(() => {
if (!focused) return;
document.addEventListener('click', onBlur);
return () => document.removeEventListener('click', onBlur);
}, [onBlur, focused]);
return (
<div className={styles.wrap} ref={wrapper}>
<TagWrapper title={input || placeholder} has_input={true} feature={feature}>
<input
type="text"
value={input}
size={1}
placeholder={placeholder}
maxLength={24}
onChange={onInput}
onKeyDown={onKeyDown}
onFocus={onFocus}
ref={ref}
/>
</TagWrapper>
{focused && input?.length > 0 && ref.current && (
<TagAutocomplete
exclude={exclude}
input={ref.current}
onSelect={onAutocompleteSelect}
search={input}
/>
)}
</div>
);
};
export { TagInput };

View file

@ -1,6 +0,0 @@
@import "src/styles/variables";
.wrap {
position: relative;
z-index: 20;
}

View file

@ -1,109 +0,0 @@
import React, { FC, HTMLAttributes, useCallback, useMemo, useState } from 'react';
import { TagField } from '~/components/containers/TagField';
import { ITag } from '~/redux/types';
import { uniq } from 'ramda';
import { Tag } from '~/components/tags/Tag';
import { TagInput } from '~/components/tags/TagInput';
import { separateTags } from '~/utils/tag';
type IProps = HTMLAttributes<HTMLDivElement> & {
tags: Partial<ITag>[];
is_deletable?: boolean;
is_editable?: boolean;
onTagsChange?: (tags: string[]) => void;
onTagClick?: (tag: Partial<ITag>) => void;
onTagDelete?: (id: ITag['ID']) => void;
};
export const Tags: FC<IProps> = ({
tags,
is_deletable,
is_editable,
onTagsChange,
onTagClick,
onTagDelete,
...props
}) => {
const [data, setData] = useState<string[]>([]);
const [catTags, ordinaryTags] = useMemo(() => separateTags(tags), [tags]);
const onSubmit = useCallback(
(last: string[]) => {
if (!onTagsChange) {
return;
}
const exist = tags.map(tag => tag.title);
const uniqueTags = uniq([...exist, ...data, ...last]).filter(el => el) as string[];
if (uniqueTags.length === exist.length) {
return;
}
onTagsChange(uniqueTags);
setData([]);
},
[data, onTagsChange, tags]
);
const onAppendTag = useCallback(
(created: string[]) => {
setData(
uniq([...data, ...created]).filter(
title => !tags.some(it => it.title?.trim() === title?.trim())
)
);
},
[data, setData, tags]
);
const onClearTag = useCallback((): string | undefined => {
if (!data.length) return;
const last = data[data.length - 1];
setData(data.slice(0, data.length - 1));
return last;
}, [data, setData]);
const exclude = useMemo(
() => [...(data || []), ...(tags || []).filter(el => el.title).map(({ title }) => title!)],
[data, tags]
);
return (
<TagField {...props}>
{catTags.map(tag => (
<Tag
key={tag.title}
tag={tag}
onClick={onTagClick}
is_deletable={is_deletable}
onDelete={onTagDelete}
/>
))}
{ordinaryTags.map(tag => (
<Tag
key={tag.title}
tag={tag}
onClick={onTagClick}
is_deletable={is_deletable}
onDelete={onTagDelete}
/>
))}
{data.map(title => (
<Tag key={title} tag={{ title }} is_editing />
))}
{is_editable && (
<TagInput
onAppend={onAppendTag}
onClearTag={onClearTag}
onSubmit={onSubmit}
exclude={exclude}
/>
)}
</TagField>
);
};