0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2025-12-24 20:42:43 +00:00

update tag setup to be based on the latest version of the stringArrayEditor.jsx

This commit is contained in:
Charlie Humphreys
2022-06-24 23:31:20 -05:00
parent 1965218e74
commit e963672b65
9 changed files with 243 additions and 127 deletions

View File

@@ -5,6 +5,7 @@ const createClass = require('create-react-class');
const _ = require('lodash');
const cx = require('classnames');
const request = require('superagent');
const StringArrayEditor = require('../stringArrayEditor/stringArrayEditor.jsx');
const SYSTEMS = ['5e', '4e', '3.5e', 'Pathfinder'];
@@ -27,38 +28,6 @@ const MetadataEditor = createClass({
onChange : ()=>{}
};
},
getInitialState : function() {
return {
tagContext : !!this.props.metadata.tags ? this.props.metadata.tags.map((tag)=>({
tag,
editing : false
})) : [],
temporaryTag : '',
updateTag : ''
};
},
componentWillUpdate : function() {
const tags = this.props.metadata.tags;
if(!!tags) {
const contextTags = Object.values(this.state.tagContext).map((context)=>context.tag);
let updateContext = tags.length !== contextTags.length;
for (let i = 0; i < tags.length && updateContext === false; i++){
const tag = tags[i];
if(!contextTags.includes(tag))
updateContext = true;
}
if(updateContext) {
this.setState((prevState)=>({
...prevState,
tagContext : this.props.metadata.tags.map((tag)=>({
tag,
editing : false
}))
}));
}
}
},
getInitialState : function(){
return {
@@ -78,9 +47,10 @@ const MetadataEditor = createClass({
},
handleFieldChange : function(name, e){
this.props.onChange(_.merge({}, this.props.metadata, {
this.props.onChange({
...this.props.metadata,
[name] : e.target.value
}));
});
},
handleSystem : function(system, e){
if(e.target.checked){
@@ -97,9 +67,10 @@ const MetadataEditor = createClass({
this.props.onChange(this.props.metadata);
},
handlePublish : function(val){
this.props.onChange(_.merge({}, this.props.metadata, {
this.props.onChange({
...this.props.metadata,
published : val
}));
});
},
handleDelete : function(){
@@ -201,68 +172,6 @@ const MetadataEditor = createClass({
</div>;
},
addTag : function(tag){
this.props.metadata.tags = _.uniq([...this.props.metadata.tags, ...(tag.split(',').map((t)=>t.trim()))]);
this.props.onChange(this.props.metadata);
},
removeTag : function(tag){
this.props.metadata.tags = this.props.metadata.tags.filter((t)=>t !== tag);
this.props.onChange(this.props.metadata);
},
updateTag : function(tag, index){
this.props.metadata.tags[index] = tag;
this.props.onChange(this.props.metadata);
},
editTag : function(index){
const tagContext = this.state.tagContext.map((context, i)=>{
context.editing = index === i;
return context;
});
this.setState({ tagContext, updateTag: this.props.metadata.tags[index] });
},
handleTagInputKeyDown : function(event, index){
if(event.key === 'Enter') {
const tagPattern = /^(?:(?:group|meta|system|type):)?[A-Za-z0-9][A-Za-z0-9 /.:\-]+$/;
if(!!event.target.value.match(tagPattern)) {
if(!!index) {
this.updateTag(event.target.value, index);
const tagContext = this.state.tagContext;
tagContext[index].editing = false;
this.setState({ tagContext, updateTag: '' });
} else {
this.addTag(event.target.value);
this.setState({ temporaryTag: '' });
}
} else {
console.log('does not match');
}
}
},
renderTags : function(){
const tagElements = Object.values(this.state.tagContext).map((context, i)=>context.editing
? <input type='text' className='value' autoFocus
value={this.state.updateTag}
key={i}
onKeyDown={(e)=>{ this.handleTagInputKeyDown(e, i); }}
onChange={(e)=>{ this.setState({ updateTag: e.target.value }); }}/>
: <div className='badge' key={i} onClick={()=>{ this.editTag(i); }}>{context.tag}&nbsp;
<i className='fa fa-times' onClick={()=>this.removeTag(context.tag)}/>
</div>
);
return <div className='field tags'>
<label>tags</label>
<div className='list'>
{tagElements}
<input type='text' className='value'
value={this.state.temporaryTag}
onKeyDown={(e)=>{ this.handleTagInputKeyDown(e); }}
onChange={(e)=>{ this.setState({ temporaryTag: e.target.value }); }}/>
</div>
</div>;
},
render : function(){
return <div className='metadataEditor'>
<div className='field title'>
@@ -289,7 +198,10 @@ const MetadataEditor = createClass({
{this.renderThumbnail()}
</div>
{this.renderTags()}
<StringArrayEditor label='tags' valuePatterns={[/^(?:(?:group|meta|system|type):)?[A-Za-z0-9][A-Za-z0-9 \/.\-]+$/]}
placeholder='add tag' unique={true}
values={this.props.metadata.tags}
onChange={(e)=>this.handleFieldChange('tags', e)}/>
{this.renderAuthors()}

View File

@@ -1,3 +1,4 @@
@import 'naturalcrit/styles/colors.less';
.metadataEditor{
position : absolute;
@@ -109,30 +110,74 @@
line-height : 1.5em;
}
.tags.field .list {
display : flex;
flex-wrap : wrap;
.field .list {
display: flex;
flex-wrap: wrap;
>* {
flex : 0 0 auto;
> * {
flex: 0 0 auto;
}
.badge {
background-color : #444;
border-radius : .5em;
color : white;
font-size : .9em;
margin : 2px;
padding : .3em;
#groupedIcon {
#backgroundColors;
display: inline-block;
border-radius: 0 0.5em 0.5em 0;
height: ~"calc(100% + 0.6em)";
position: relative;
top: -0.3em;
right: -0.3em;
cursor: pointer;
min-width: 20px;
text-align: center;
color: white;
i.fa-times {
cursor: pointer;
i {
position: relative;
top: 50%;
transform: translateY(-50%);
}
}
.value {
width : 7.5vw;
min-width : 75px;
.badge {
background-color: #dddddd;
border-radius: .5em;
font-size: .9em;
margin: 2px;
padding: .3em;
.icon {
#groupedIcon
}
}
.input-group {
height: ~"calc(.9em + 4px + .6em)";
input {
border-radius: .5em 0 0 .5em;
}
input:last-child {
border-radius: .5em;
}
.value {
width: 7.5vw;
min-width: 75px;
height: 100%;
}
.invalid:focus {
background-color: pink;
}
.icon {
#groupedIcon;
height: 97%;
font-size: .8em;
right: 1px;
top: -.54em;
}
}
}
}

View File

@@ -0,0 +1,133 @@
const React = require('react');
const createClass = require('create-react-class');
const _ = require('lodash');
const StringArrayEditor = createClass({
displayName : 'StringArrayEditor',
getDefaultProps : function() {
return {
label : '',
values : [],
valuePatterns : null,
placeholder : '',
unique : false,
cannotEdit : [],
onChange : ()=>{}
};
},
getInitialState : function() {
return {
valueContext : !!this.props.values ? this.props.values.map((value)=>({
value,
editing : false
})) : [],
temporaryValue : '',
updateValue : ''
};
},
componentDidUpdate : function(prevProps) {
if(!_.eq(this.props, prevProps)) {
this.setState({
valueContext : !!this.props.values ? this.props.values.map((newValue)=>({
value : newValue,
editing : this.state.valueContext.find(({ value })=>value === newValue)?.editing || false
})) : []
});
}
},
handleChange : function(value) {
this.props.onChange({
target : {
value
}
});
},
addValue : function(value){
this.handleChange(_.uniq([...this.props.values, value]));
this.setState({
temporaryValue : ''
});
},
removeValue : function(index){
this.handleChange(this.props.values.filter((_, i)=>i !== index));
},
updateValue : function(value, index){
const valueContext = this.state.valueContext;
valueContext[index].value = value;
valueContext[index].editing = false;
this.handleChange(valueContext.map((context)=>context.value));
this.setState({ valueContext, updateValue: '' });
},
editValue : function(index){
if(!!this.props.cannotEdit && this.props.cannotEdit.includes(this.props.values[index])) {
return;
}
const valueContext = this.state.valueContext.map((context, i)=>{
context.editing = index === i;
return context;
});
this.setState({ valueContext, updateValue: this.props.values[index] });
},
valueIsValid : function(value) {
const matchesPatterns = !this.props.valuePatterns || this.props.valuePatterns.some((pattern)=>!!(value || '').match(pattern));
const uniqueIfSet = !this.props.unique || !this.props.values.includes(value);
return matchesPatterns && uniqueIfSet;
},
handleValueInputKeyDown : function(event, index) {
if(event.key === 'Enter') {
if(this.valueIsValid(event.target.value)) {
if(index !== undefined) {
this.updateValue(event.target.value, index);
} else {
this.addValue(event.target.value);
}
}
} else if(event.key === 'Escape') {
const valueContext = this.state.valueContext;
valueContext[index].editing = false;
this.setState({ valueContext, updateValue: '' });
}
},
render : function() {
const valueElements = Object.values(this.state.valueContext).map((context, i)=>context.editing
? <React.Fragment key={i}>
<div className='input-group'>
<input type='text' className={`value ${this.valueIsValid(this.state.updateValue) ? '' : 'invalid'}`} autoFocus placeholder={this.props.placeholder}
value={this.state.updateValue}
onKeyDown={(e)=>this.handleValueInputKeyDown(e, i)}
onChange={(e)=>this.setState({ updateValue: e.target.value })}/>
{this.valueIsValid(this.state.updateValue) ? <div className='icon steel'><i className='fa fa-check fa-fw' onClick={(e)=>{ e.stopPropagation(); this.updateValue(this.state.updateValue, i); }}/></div> : null}
</div>
</React.Fragment>
: <div className='badge' key={i} onClick={()=>this.editValue(i)}>{context.value}
{!!this.props.cannotEdit && this.props.cannotEdit.includes(context.value) ? null : <div className='icon steel'><i className='fa fa-times fa-fw' onClick={(e)=>{ e.stopPropagation(); this.removeValue(i); }}/></div>}
</div>
);
return <div className='field values'>
<label>{this.props.label}</label>
<div className='list'>
{valueElements}
<div className='input-group'>
<input type='text' className={`value ${this.valueIsValid(this.state.temporaryValue) ? '' : 'invalid'}`} placeholder={this.props.placeholder}
value={this.state.temporaryValue}
onKeyDown={(e)=>this.handleValueInputKeyDown(e)}
onChange={(e)=>this.setState({ temporaryValue: e.target.value })}/>
{this.valueIsValid(this.state.temporaryValue) ? <div className='icon steel'><i className='fa fa-check fa-fw' onClick={(e)=>{ e.stopPropagation(); this.addValue(this.state.temporaryValue); }}/></div> : null}
</div>
</div>
</div>;
}
});
module.exports = StringArrayEditor;

View File

@@ -91,7 +91,7 @@
&:nth-of-type(2){ background-color: darken(@purple, 30%); }
}
.item{
#backgroundColors;
#backgroundColorsHover;
.animate(background-color);
position : relative;
display : block;

View File

@@ -114,7 +114,7 @@ const EditPage = createClass({
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
this.setState((prevState)=>({
brew : _.merge({}, prevState.brew, { text: text }),
brew : { ...prevState.brew, text: text },
isPending : true,
htmlErrors : htmlErrors
}), ()=>this.trySave());
@@ -122,14 +122,17 @@ const EditPage = createClass({
handleStyleChange : function(style){
this.setState((prevState)=>({
brew : _.merge({}, prevState.brew, { style: style }),
brew : { ...prevState.brew, style: style },
isPending : true
}), ()=>this.trySave());
},
handleMetaChange : function(metadata){
this.setState((prevState)=>({
brew : _.merge({}, prevState.brew, metadata),
brew : {
...prevState.brew,
...metadata
},
isPending : true,
}), ()=>this.trySave());
@@ -213,11 +216,11 @@ const EditPage = createClass({
history.replaceState(null, null, `/edit/${this.savedBrew.editId}`);
this.setState((prevState)=>({
brew : _.merge({}, prevState.brew, {
brew : { ...prevState.brew,
googleId : this.savedBrew.googleId ? this.savedBrew.googleId : null,
editId : this.savedBrew.editId,
shareId : this.savedBrew.shareId
}),
},
isPending : false,
isSaving : false,
}));

View File

@@ -52,7 +52,7 @@ const HomePage = createClass({
},
handleTextChange : function(text){
this.setState((prevState)=>({
brew : _.merge({}, prevState.brew, { text: text })
brew : { ...prevState.brew, text: text }
}));
},
renderNavbar : function(){

View File

@@ -112,7 +112,7 @@ const NewPage = createClass({
if(htmlErrors.length) htmlErrors = Markdown.validate(text);
this.setState((prevState)=>({
brew : _.merge({}, prevState.brew, { text: text }),
brew : { ...prevState.brew, text: text },
htmlErrors : htmlErrors
}));
localStorage.setItem(BREWKEY, text);
@@ -120,14 +120,14 @@ const NewPage = createClass({
handleStyleChange : function(style){
this.setState((prevState)=>({
brew : _.merge({}, prevState.brew, { style: style }),
brew : { ...prevState.brew, style: style },
}));
localStorage.setItem(STYLEKEY, style);
},
handleMetaChange : function(metadata){
this.setState((prevState)=>({
brew : _.merge({}, prevState.brew, metadata),
brew : { ...prevState.brew, ...metadata },
}));
localStorage.setItem(METAKEY, JSON.stringify({
// 'title' : this.state.brew.title,

View File

@@ -50,7 +50,7 @@ nav{
}
}
.navItem{
#backgroundColors;
#backgroundColorsHover;
.animate(background-color);
padding : 8px 12px;
cursor : pointer;

View File

@@ -23,6 +23,29 @@
@grey : #7F8C8D;
#backgroundColors {
&.tealLight{ background-color : @tealLight };
&.teal{ background-color : @teal };
&.greenLight{ background-color : @greenLight };
&.green{ background-color : @green };
&.blueLight{ background-color : @blueLight };
&.blue{ background-color : @blue };
&.purpleLight{ background-color : @purpleLight };
&.purple{ background-color : @purple };
&.steelLight{ background-color : @steelLight };
&.steel{ background-color : @steel };
&.yellowLight{ background-color : @yellowLight };
&.yellow{ background-color : @yellow };
&.orangeLight{ background-color : @orangeLight };
&.orange{ background-color : @orange };
&.redLight{ background-color : @redLight };
&.red{ background-color : @red };
&.silverLight{ background-color : @silverLight };
&.silver{ background-color : @silver };
&.greyLight{ background-color : @greyLight };
&.grey{ background-color : @grey };
}
#backgroundColorsHover {
&.tealLight:hover{ background-color : @tealLight };
&.teal:hover{ background-color : @teal };
&.greenLight:hover{ background-color : @greenLight };