0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-27 20:23:08 +00:00

add image selector field type, modal component, and new snippet widgets

This commit is contained in:
Charlie Humphreys
2023-07-16 02:14:43 -05:00
parent e6055bd417
commit bef6b94dc4
10 changed files with 301 additions and 17 deletions

View File

@@ -12,8 +12,9 @@ export const SNIPPET_TYPE = {
};
export const FIELD_TYPE = {
TEXT : 0,
CHECKBOX : 1
TEXT : 0,
CHECKBOX : 1,
IMAGE_SELECTOR : 2,
};
export const PATTERNS = {
@@ -23,7 +24,8 @@ export const PATTERNS = {
[SNIPPET_TYPE.INJECTOR] : ()=>new RegExp(`^\\!\\[(?:[a-zA-Z -]+)?\\]\\(.*\\).*{[a-zA-Z0-9:, "'-]+}$`),
},
field : {
[FIELD_TYPE.TEXT] : (name)=>new RegExp(`[{,;](${name}):([^};,"\\(]*\\((?!,)[^};"\\)]*\\)|"[^},;"]*"|[^},;]*)`),
[FIELD_TYPE.TEXT] : (name)=>new RegExp(`[{,;](${name}):([^};,"\\(]*\\((?!,)[^};"\\)]*\\)|"[^},;"]*"|[^},;]*)`),
[FIELD_TYPE.IMAGE_SELECTOR] : (name)=>new RegExp(`{{(${name})(\\d*)`),
},
collectStyles : new RegExp(`(?:([a-zA-Z-]+):(?!\\/))+`, 'g'),
};

View File

@@ -0,0 +1,86 @@
require('./image-selector.less');
const React = require('react');
const ReactDOMClient = require('react-dom/client');
const createClass = require('create-react-class');
const Modal = require('../modal/modal.jsx');
const { PATTERNS } = require('../constants.js');
const CodeMirror = require('../../../code-mirror.js');
const _ = require('lodash');
const ImageSelector = createClass({
modalRef : React.createRef(),
getDefaultProps : function () {
return {
field : {},
cm : {},
n : undefined
};
},
getInitialState : function() {
return {
selected : undefined,
};
},
componentDidMount : function() {
const el = document.createElement('div');
const root = ReactDOMClient.createRoot(el);
document.querySelector('body').append(el);
this.setState({
el,
modalRoot : root
});
},
componentDidUpdate : function() {
const { name, preview, values } = this.props.field;
const { selected } = this.state;
const images = values.map((v, i)=>{
const className = selected === v ? 'selected' : '';
return <img key={i} className={className} src={preview(v)} alt={`${name} image ${v}`} onClick={()=>this.select(v)}/>;
});
this.state.modalRoot?.render(<Modal ref={this.modalRef} header={_.startCase(name)} save={this.save}>
<div className={'images'}>
{images}
</div>
</Modal>);
},
componentWillUnmount : function() {
this.state.el.remove();
},
save : function() {
const { cm, field, n } = this.props;
const { text } = cm.lineInfo(n);
const pattern = PATTERNS.field[field.type](field.name);
const [fullmatch, label, current] = text.match(pattern);
if(!fullmatch) {
console.warn('something is wrong... please report this warning with a screenshot');
return;
}
const currentText = `${label}${current ?? ''}`;
const index = 2;
const value = label + this.state.selected;
console.log(text, pattern, currentText, value, fullmatch, label, current);
cm.replaceRange(value, CodeMirror.Pos(n, index), CodeMirror.Pos(n, index + currentText.length), '+insert');
},
select : function(value) {
this.setState({
selected : value
});
},
render : function () {
return <React.Fragment>
<button onClick={()=>this.modalRef.current.setVisible(true)}>Select Image</button>
</React.Fragment>;
}
});
module.exports = ImageSelector;

View File

@@ -0,0 +1,21 @@
.images {
display: flex;
flex-direction: row;
flex-wrap: wrap;
img {
flex: 0 0 auto;
max-width: 10vw;
max-height: 20vh;
border-radius: 10px;
&:hover {
background-color: rgba(0, 0, 0, .1);
}
&.selected {
background-color: rgba(0, 0, 0, .175);
}
}
}

View File

@@ -1,7 +1,9 @@
const Text = require('./text/text.jsx');
const Checkbox = require('./checkbox/checkbox.jsx');
const ImageSelector = require('./image-selector/image-selector.jsx');
module.exports = {
Text : Text,
Checkbox : Checkbox,
Text : Text,
Checkbox : Checkbox,
ImageSelector : ImageSelector
};

View File

@@ -0,0 +1,49 @@
require('./modal.less');
const React = require('react');
const createClass = require('create-react-class');
const Modal = createClass({
getDefaultProps : function () {
return {
header : '',
save : ()=>{},
};
},
getInitialState : function() {
return {
visible : false,
};
},
setVisible : function(visible) {
this.setState({
visible
});
},
save : function() {
this.props.save();
this.setVisible(false);
},
render : function () {
const { children, header } = this.props;
const { visible } = this.state;
return <React.Fragment>
{visible ? <div className={'bg-cover'}>
<div className={'modal'}>
<h1>{header}</h1>
<hr/>
{children}
<div className={'action-row'}>
<button id={'save'} onClick={()=>this.save()}>Save</button>
<button id={'cancel'} onClick={()=>this.setVisible(false)}>Cancel</button>
</div>
</div>
</div> : null}
</React.Fragment>;
}
});
module.exports = Modal;

View File

@@ -0,0 +1,58 @@
@import 'naturalcrit/styles/colors.less';
.bg-cover {
width: 100vw;
height: 100vh;
position: absolute;
z-index: 10000000;
top: 0;
left: 0;
background-color: rgba(0, 0, 0, .5);
}
.modal {
position: absolute;
top: 10vh;
left: 25vw;
width: 50vw;
min-height: 50vh;
max-height: 80vh;
background-color: #fff;
border-radius: 10px;
box-shadow: 5px 5px 50px black;
display: flex;
flex-direction: column;
justify-content: space-between;
h1 {
margin-top: 5px;
margin-left: 5px;
font-size: 2em;
}
>* {
flex: 0 0 auto;
}
.action-row {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: left;
>* {
flex: 0 0 auto;
margin-left: 5px;
}
}
button {
&#cancel {
background-color: @redLight;
&:hover {
background-color: @red;
}
}
}
}

View File

@@ -47,7 +47,7 @@ const Text = createClass({
if(this.state.value !== value) {
const { field } = this.props;
this.props.setHints(this, this.props.getStyleHints(field, field.hints ? this.state.value : []));
this.props.setHints(this, field.hints ? this.props.getStyleHints(field, this.state.value) : []);
}
},