feat: add input tag to data object

This commit is contained in:
Akuoko Daniel Jnr 2021-03-18 13:16:37 +00:00
parent 743fa736e9
commit d692974fd7
No known key found for this signature in database
GPG Key ID: 1C95803CACD3E9DC
5 changed files with 256 additions and 37 deletions

View File

@ -59,6 +59,8 @@ export const system = {
bgBlurGrayBlack: "rgba(15, 14, 18, 0.8)", bgBlurGrayBlack: "rgba(15, 14, 18, 0.8)",
bgBlurBlack: "rgba(15, 14, 18, 0.9)", bgBlurBlack: "rgba(15, 14, 18, 0.9)",
active: "#00BB00", active: "#00BB00",
blurBlack: "#262626",
bgBlurGray: "#403F42",
}; };
export const shadow = { export const shadow = {

View File

@ -16,6 +16,7 @@ import { SlatePicker } from "~/components/core/SlatePicker";
import { Input } from "~/components/system/components/Input"; import { Input } from "~/components/system/components/Input";
import { Boundary } from "~/components/system/components/fragments/Boundary"; import { Boundary } from "~/components/system/components/fragments/Boundary";
import { Toggle } from "~/components/system/components/Toggle"; import { Toggle } from "~/components/system/components/Toggle";
import { Tag } from "~/components/system/components/Tag";
const DEFAULT_BOOK = const DEFAULT_BOOK =
"https://slate.textile.io/ipfs/bafkreibk32sw7arspy5kw3p5gkuidfcwjbwqyjdktd5wkqqxahvkm2qlyi"; "https://slate.textile.io/ipfs/bafkreibk32sw7arspy5kw3p5gkuidfcwjbwqyjdktd5wkqqxahvkm2qlyi";
@ -260,6 +261,11 @@ class CarouselSidebarData extends React.Component {
unsavedChanges: false, unsavedChanges: false,
isEditing: false, isEditing: false,
isDownloading: false, isDownloading: false,
tags:
!Array.isArray(this.props.data.tags) || this.props.data.tags?.length === 0
? []
: this.props.data.tags,
suggestions: [],
}; };
componentDidMount = () => { componentDidMount = () => {
@ -279,6 +285,8 @@ class CarouselSidebarData extends React.Component {
} }
this.setState({ selected, inPublicSlates, isPublic: this.props.data.public }); this.setState({ selected, inPublicSlates, isPublic: this.props.data.public });
} }
this.fetchSuggestions();
}; };
_handleDarkMode = async (e) => { _handleDarkMode = async (e) => {
@ -296,8 +304,8 @@ class CarouselSidebarData extends React.Component {
}; };
_handleSave = async () => { _handleSave = async () => {
let name = { name: this.state.name }; let data = { name: this.state.name, tags: this.state.tags };
this.props.onSave(name, this.props.index); this.props.onSave(data, this.props.index);
await setTimeout(() => { await setTimeout(() => {
this.setState({ unsavedChanges: false }); this.setState({ unsavedChanges: false });
}, 500); }, 500);
@ -458,6 +466,23 @@ class CarouselSidebarData extends React.Component {
} }
}; };
_handleTagDelete = async (tag) => {
const response = await Actions.deleteTag({ tag });
if (response.success) {
this.setState({ suggestions: response.tags });
}
if (Events.hasError(response)) {
return;
}
};
fetchSuggestions = async () => {
const res = await Actions.getTagsByUserId();
this.setState({ suggestions: res.tags });
};
render() { render() {
const isVisible = this.state.inPublicSlates || this.state.isPublic; const isVisible = this.state.inPublicSlates || this.state.isPublic;
const { cid, file, name, coverImage, type, size, url, blurhash } = this.props.data; const { cid, file, name, coverImage, type, size, url, blurhash } = this.props.data;
@ -547,6 +572,27 @@ class CarouselSidebarData extends React.Component {
</div> </div>
) : null} ) : null}
</div> </div>
{this.props.isOwner ? (
<React.Fragment>
<div css={STYLES_SECTION_HEADER} style={{ margin: "48px 0px 8px 0px" }}>
Tags
</div>
<div css={STYLES_OPTIONS_SECTION}>
<Tag
type="dark"
name="tags"
placeholder={`Edit tags for ${this.state.name}`}
tags={this.state.tags}
suggestions={this.state.suggestions}
style={{ margin: "0 0 16px" }}
inputStyles={{ padding: "16px" }}
dropdownStyles={{ top: "50px" }}
onChange={this._handleChange}
handleTagDelete={this._handleTagDelete}
/>
</div>
</React.Fragment>
) : null}
{this.props.external ? null : ( {this.props.external ? null : (
<React.Fragment> <React.Fragment>
<div css={STYLES_SECTION_HEADER}>Connected Slates</div> <div css={STYLES_SECTION_HEADER}>Connected Slates</div>

View File

@ -12,6 +12,7 @@ import { LoaderSpinner } from "~/components/system/components/Loaders";
import { SlatePicker } from "~/components/core/SlatePicker"; import { SlatePicker } from "~/components/core/SlatePicker";
import { Input } from "~/components/system/components/Input"; import { Input } from "~/components/system/components/Input";
import { Textarea } from "~/components/system/components/Textarea"; import { Textarea } from "~/components/system/components/Textarea";
import { Tag } from "~/components/system/components/Tag";
import ProcessedText from "~/components/core/ProcessedText"; import ProcessedText from "~/components/core/ProcessedText";
@ -186,6 +187,11 @@ export default class CarouselSidebarSlate extends React.Component {
body: Strings.isEmpty(this.props.data.body) ? "" : this.props.data.body, body: Strings.isEmpty(this.props.data.body) ? "" : this.props.data.body,
source: Strings.isEmpty(this.props.data.source) ? "" : this.props.data.source, source: Strings.isEmpty(this.props.data.source) ? "" : this.props.data.source,
author: Strings.isEmpty(this.props.data.author) ? "" : this.props.data.author, author: Strings.isEmpty(this.props.data.author) ? "" : this.props.data.author,
tags:
!Array.isArray(this.props.data.tags) || this.props.data.tags?.length === 0
? []
: this.props.data.tags,
suggestions: [],
selected: {}, selected: {},
isPublic: false, isPublic: false,
copyValue: "", copyValue: "",
@ -213,6 +219,8 @@ export default class CarouselSidebarSlate extends React.Component {
} }
this.setState({ selected, isPublic }); this.setState({ selected, isPublic });
} }
this.fetchSuggestions();
}; };
_handleClose = () => { _handleClose = () => {
@ -228,6 +236,7 @@ export default class CarouselSidebarSlate extends React.Component {
body: this.state.body, body: this.state.body,
source: this.state.source, source: this.state.source,
author: this.state.author, author: this.state.author,
tags: this.state.tags,
}; };
this.props.onSave(data, this.props.index); this.props.onSave(data, this.props.index);
this.setState({ unsavedChanges: false }); this.setState({ unsavedChanges: false });
@ -321,6 +330,23 @@ export default class CarouselSidebarSlate extends React.Component {
} }
}; };
_handleTagDelete = async (tag) => {
const response = await Actions.deleteTag({ tag });
if (response.success) {
this.setState({ suggestions: response.tags });
}
if (Events.hasError(response)) {
return;
}
};
fetchSuggestions = async () => {
const res = await Actions.getTagsByUserId();
this.setState({ suggestions: res.tags });
};
render() { render() {
let isUnityGame = false; let isUnityGame = false;
if (this.props.data.type === "application/unity") { if (this.props.data.type === "application/unity") {
@ -350,6 +376,18 @@ export default class CarouselSidebarSlate extends React.Component {
onChange={this._handleChange} onChange={this._handleChange}
style={STYLES_INPUT} style={STYLES_INPUT}
/> />
<Tag
type="dark"
name="tags"
placeholder={`Edit tags for ${this.state.title}`}
tags={this.state.tags}
suggestions={this.state.suggestions}
style={{ margin: "0 0 16px" }}
inputStyles={{ padding: "16px" }}
dropdownStyles={{ top: "50px" }}
onChange={this._handleChange}
handleTagDelete={this._handleTagDelete}
/>
<Input <Input
full full
value={this.state.source} value={this.state.source}

View File

@ -53,9 +53,8 @@ export default class SidebarSingleSlateSettings extends React.Component {
suggestions: [], suggestions: [],
}; };
async componentDidMount() { componentDidMount() {
const res = await Actions.getTagsByUserId(); this.fetchSuggestions();
this.setState({ suggestions: res.tags });
} }
_handleSubmit = async () => { _handleSubmit = async () => {
@ -131,6 +130,11 @@ export default class SidebarSingleSlateSettings extends React.Component {
} }
}; };
fetchSuggestions = async () => {
const res = await Actions.getTagsByUserId();
this.setState({ suggestions: res.tags });
};
render() { render() {
const slug = Strings.createSlug(this.state.name); const slug = Strings.createSlug(this.state.name);
const url = `/${this.props.viewer.username}/${slug}`; const url = `/${this.props.viewer.username}/${slug}`;
@ -229,7 +233,7 @@ export default class SidebarSingleSlateSettings extends React.Component {
</System.P> </System.P>
<System.Tag <System.Tag
name="tags" name="tags"
placeholder={`Edit tag for ${this.state.slatename}`} placeholder={`Edit tags for ${this.state.slatename}`}
tags={this.state.tags} tags={this.state.tags}
suggestions={this.state.suggestions} suggestions={this.state.suggestions}
style={{ marginTop: 12 }} style={{ marginTop: 12 }}

View File

@ -26,17 +26,50 @@ const STYLES_DROPDOWN = css`
width: 100%; width: 100%;
z-index: 30; z-index: 30;
box-shadow: 0px 12px 24px rgba(178, 178, 178, 0.3); box-shadow: 0px 12px 24px rgba(178, 178, 178, 0.3);
li[data-item-active="true"] {
background: ${Constants.system.gray80};
span,
svg {
color: ${Constants.system.white};
}
}
`; `;
const STYLES_DROPDOWN_ITEM = css` const DROPDOWN_ITEM_STYLES = `
list-style-type: none; list-style-type: none;
padding: 8px 12px; padding: 8px 12px;
background: ${Constants.system.white};
border: 0.5px solid ${Constants.system.gray20};
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
span {
font-size: 14px;
line-height: 1.5;
color: ${Constants.system.textGray};
font-family: ${Constants.font.text};
margin: 0 0 0 8px;
}
div.dismiss {
opacity: 0;
&:hover {
color: ${Constants.system.newBlack};
}
}
`;
const STYLES_DROPDOWN_ITEM = css`
${DROPDOWN_ITEM_STYLES};
background: ${Constants.system.white};
border: 0.5px solid ${Constants.system.gray20};
&:hover { &:hover {
background: ${Constants.system.gray10}; background: ${Constants.system.gray10};
@ -50,25 +83,40 @@ const STYLES_DROPDOWN_ITEM = css`
} }
} }
span { div.active {
font-size: 14px; background: linear-gradient(105.04deg, #e3e3e3 7.95%, #f3f3f3 92.61%);
line-height: 1.5; border-radius: 50px;
color: ${Constants.system.textGray};
font-family: ${Constants.font.text};
margin: 0 0 0 8px;
} }
`;
div.dismiss { const STYLES_DROPDOWN_ITEM_DARK = css`
opacity: 0; ${DROPDOWN_ITEM_STYLES};
&:hover { background: ${Constants.system.bgBlurGray};
color: ${Constants.system.newBlack}; border: 0.5px solid ${Constants.system.bgBlurGray};
&:hover {
background: ${Constants.system.gray80};
span,
div:not(.dismiss) {
color: ${Constants.system.white};
}
div.dismiss {
opacity: 1;
} }
} }
div.active { div.active {
background: linear-gradient(105.04deg, #e3e3e3 7.95%, #f3f3f3 92.61%); background: linear-gradient(90deg, rgba(134, 134, 136, 1) 0%, rgba(134, 134, 136, 0.2) 80%);
border-radius: 50px; border-radius: 50px;
span {
padding: 5px;
display: flex;
align-items: center;
}
} }
`; `;
@ -88,11 +136,9 @@ const STYLES_DROPDOWN_ITEM_ICON = css`
} }
`; `;
const STYLES_DROPDOWN_ADD_ITEM = css` const DROPDOWN_ITEM_ADD_STYLES = `
list-style-type: none; list-style-type: none;
padding: 8px 12px; padding: 8px 12px;
background: ${Constants.system.white};
border: 0.5px solid ${Constants.system.gray20};
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
@ -100,9 +146,19 @@ const STYLES_DROPDOWN_ADD_ITEM = css`
span { span {
font-size: 14px; font-size: 14px;
line-height: 1.5; line-height: 1.5;
color: ${Constants.system.newBlack};
font-family: ${Constants.font.text}; font-family: ${Constants.font.text};
} }
`;
const STYLES_DROPDOWN_ADD_ITEM = css`
${DROPDOWN_ITEM_ADD_STYLES};
background: ${Constants.system.white};
border: 0.5px solid ${Constants.system.gray20};
span {
color: ${Constants.system.newBlack};
}
span.value { span.value {
background: ${Constants.system.bgGray}; background: ${Constants.system.bgGray};
@ -117,14 +173,39 @@ const STYLES_DROPDOWN_ADD_ITEM = css`
} }
`; `;
const STYLES_DROPDOWN_ADD_ITEM_DARK = css`
${DROPDOWN_ITEM_ADD_STYLES};
background: ${Constants.system.bgBlurGray};
border: 0.5px solid ${Constants.system.bgBlurGray};
span,
svg {
color: ${Constants.system.textGray};
}
span.value {
background: ${Constants.system.gray70};
color: ${Constants.system.white};
}
&:hover {
background: ${Constants.system.gray80};
span,
svg {
color: ${Constants.system.white};
}
}
`;
const INPUT_STYLES = ` const INPUT_STYLES = `
box-sizing: border-box; box-sizing: border-box;
font-family: ${Constants.font.text}; font-family: ${Constants.font.text};
-webkit-appearance: none; -webkit-appearance: none;
background: ${Constants.system.white};
color: ${Constants.system.black};
border-radius: 4px; border-radius: 4px;
display: flex; display: flex;
padding: 8px 12px;
font-size: 14px; font-size: 14px;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
@ -138,10 +219,11 @@ const STYLES_INPUT = css`
${INPUT_STYLES}; ${INPUT_STYLES};
width: 100%; width: 100%;
padding: 8px 12px;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
box-shadow: 0 0 0 1px ${Constants.system.gray30} inset; box-shadow: 0 0 0 1px ${Constants.system.gray30} inset;
background: ${Constants.system.white};
color: ${Constants.system.black};
:focus { :focus {
outline: 0; outline: 0;
@ -166,6 +248,17 @@ const STYLES_INPUT = css`
} }
`; `;
const STYLES_INPUT_DARK = css`
${INPUT_STYLES};
width: 100%;
text-overflow: ellipsis;
white-space: nowrap;
box-shadow: 0 0 0 1px #3c3c3c inset;
background: ${Constants.system.blurBlack};
color: ${Constants.system.white};
`;
const STYLES_LIST = css` const STYLES_LIST = css`
display: inline-flex; display: inline-flex;
flex-wrap: wrap; flex-wrap: wrap;
@ -173,11 +266,9 @@ const STYLES_LIST = css`
border-radius: 4px; border-radius: 4px;
`; `;
const STYLES_TAG = css` const TAG_STYLES = `
list-style-type: none; list-style-type: none;
border-radius: 4px; border-radius: 4px;
background: ${Constants.system.bgGray};
color: ${Constants.system.newBlack};
font-family: ${Constants.font.text}; font-family: ${Constants.font.text};
padding: 2px 8px; padding: 2px 8px;
margin: 8px 8px 0 0; margin: 8px 8px 0 0;
@ -186,12 +277,30 @@ const STYLES_TAG = css`
line-height: 1.5; line-height: 1.5;
font-size: 14px; font-size: 14px;
} }
`;
const STYLES_TAG = css`
${TAG_STYLES};
background: ${Constants.system.bgGray};
color: ${Constants.system.newBlack};
&:hover { &:hover {
background: ${Constants.system.gray30}; background: ${Constants.system.gray30};
} }
`; `;
const STYLES_TAG_DARK = css`
${TAG_STYLES};
background: ${Constants.system.gray80};
color: ${Constants.system.textGray};
&:hover {
background: ${Constants.system.gray80};
}
`;
const STYLES_SHOW_MORE = css` const STYLES_SHOW_MORE = css`
font-family: ${Constants.font.text}; font-family: ${Constants.font.text};
color: ${Constants.system.textGray}; color: ${Constants.system.textGray};
@ -252,11 +361,13 @@ const DeleteConfirmation = ({ tag, handleDelete }) => {
}; };
const Dropdown = ({ const Dropdown = ({
type,
open, open,
setOpen, setOpen,
tags, tags,
value, value,
suggestions, suggestions,
dropdownStyles,
handleAdd, handleAdd,
handleRemove, handleRemove,
handleTagDelete, handleTagDelete,
@ -293,9 +404,16 @@ const Dropdown = ({
return ( return (
<div ref={dropdownEl}> <div ref={dropdownEl}>
<ul css={STYLES_DROPDOWN} style={{ display: open ? "block" : "none" }}> <ul
css={STYLES_DROPDOWN}
style={{
display: open ? "block" : "none",
boxShadow: type === "dark" && "0px 12px 24px rgba(0, 0, 0, 0.2)",
...dropdownStyles,
}}
>
{!suggestions.length ? ( {!suggestions.length ? (
<li css={STYLES_DROPDOWN_ITEM}> <li css={type === "dark" ? STYLES_DROPDOWN_ITEM_DARK : STYLES_DROPDOWN_ITEM}>
<LoaderSpinner style={{ height: "24px", width: "24px", margin: "0 auto" }} /> <LoaderSpinner style={{ height: "24px", width: "24px", margin: "0 auto" }} />
</li> </li>
) : ( ) : (
@ -303,8 +421,9 @@ const Dropdown = ({
{(filteredTags || []).map((tag, index) => ( {(filteredTags || []).map((tag, index) => (
<li <li
key={tag} key={tag}
css={STYLES_DROPDOWN_ITEM} css={type === "dark" ? STYLES_DROPDOWN_ITEM_DARK : STYLES_DROPDOWN_ITEM}
onClick={isActiveTag(index) ? () => handleRemove(tag) : () => handleAdd(tag)} onClick={isActiveTag(index) ? () => handleRemove(tag) : () => handleAdd(tag)}
data-item-active={isActiveTag(index)}
> >
{isActiveTag(index) && ( {isActiveTag(index) && (
<div css={STYLES_DROPDOWN_ITEM_ICON}> <div css={STYLES_DROPDOWN_ITEM_ICON}>
@ -330,7 +449,7 @@ const Dropdown = ({
</li> </li>
))} ))}
<li <li
css={STYLES_DROPDOWN_ADD_ITEM} css={type === "dark" ? STYLES_DROPDOWN_ADD_ITEM_DARK : STYLES_DROPDOWN_ADD_ITEM}
onClick={() => { onClick={() => {
if (value) { if (value) {
handleAdd(value); handleAdd(value);
@ -341,7 +460,11 @@ const Dropdown = ({
<SVG.Plus height="16px" /> <SVG.Plus height="16px" />
<span style={{ margin: "0 8px" }}>create new tag</span> <span style={{ margin: "0 8px" }}>create new tag</span>
{value && ( {value && (
<span css={STYLES_TAG} className="value" style={{ margin: 0 }}> <span
css={type === "dark" ? STYLES_TAG_DARK : STYLES_TAG}
className="value"
style={{ margin: 0 }}
>
{value} {value}
</span> </span>
)} )}
@ -354,10 +477,13 @@ const Dropdown = ({
}; };
export const Tag = ({ export const Tag = ({
type,
name, name,
tags, tags,
suggestions = [], suggestions = [],
style, style,
inputStyles,
dropdownStyles,
placeholder, placeholder,
handleTagDelete, handleTagDelete,
onChange, onChange,
@ -401,18 +527,21 @@ export const Tag = ({
<input <input
name={name} name={name}
type="text" type="text"
css={STYLES_INPUT} css={type === "dark" ? STYLES_INPUT_DARK : STYLES_INPUT}
style={{ ...inputStyles }}
placeholder={placeholder ? placeholder : null} placeholder={placeholder ? placeholder : null}
value={value} value={value}
onChange={_handleChange} onChange={_handleChange}
onFocus={_handleFocus} onFocus={_handleFocus}
/> />
<Dropdown <Dropdown
type={type}
open={open} open={open}
setOpen={setOpen} setOpen={setOpen}
tags={tags} tags={tags}
suggestions={suggestions} suggestions={suggestions}
value={value} value={value}
dropdownStyles={dropdownStyles}
handleAdd={_handleAdd} handleAdd={_handleAdd}
handleRemove={_handleRemove} handleRemove={_handleRemove}
handleTagDelete={handleTagDelete} handleTagDelete={handleTagDelete}
@ -422,7 +551,7 @@ export const Tag = ({
<ul css={STYLES_LIST}> <ul css={STYLES_LIST}>
{tags && {tags &&
tags.slice(0, numToDisplay).map((tag) => ( tags.slice(0, numToDisplay).map((tag) => (
<li key={tag} css={STYLES_TAG}> <li key={tag} css={type === "dark" ? STYLES_TAG_DARK : STYLES_TAG}>
<span>{tag}</span> <span>{tag}</span>
</li> </li>
))} ))}