I tried to implement this HTML Text demo with React Konva, but in vain, because - html

What do I want to do:
I want to make React Konva Text editable. That is, if I double click on the Text... (I can move it to anywhere in the Konva Stage) ...I can show a text area to get the edits from the user, any changes he/she would like to do to the default text.
Conditions:
Enter key (keyboard) should produce a new line.
Double click on Text should show this text area on the same offset X and Y of this Text.
Double click on div should take back to Text. Now if there was any change done to the text, then it should get reflected on the Text component's label
I tried to implement this HTML Text demo with React Konva, but in vain, because of limitations
The html Konva demo that I want to reproduce in React Konva
Things I did:
Since HTML 5.0 is incompatible if used in conjugation with React Konva components, like Text, Image, RegularPolygon ... etc.
I used { Html } from 'react-konva-utils' so that I could move that content along with the Text component like in the demo in the link above.
Things that I observed:
The HTML can take padding and margin (we can use normal html within tag.), but not top, left.
I did try to put X property of Text in the margin top of the root div and value of Text's Y property in the margin left attribute of the same div, but had to revert as it was not close to the demo above.
Code:
import React, { useState, useRef, useEffect, Fragment } from "react";
import { Html } from 'react-konva-utils';
import { Text, Transformer } from "react-konva";
/*
Konva warning: tr.setNode(shape), tr.node(shape) and tr.attachTo(shape) methods are deprecated. Please use tr.nodes(nodesArray) instead.
*/
const KText = ({ stage, id, properties, isSelected, onSelect, onChange, setActiveText }) => {
const shapeRef = useRef();
const trRef = useRef();
const [toggleEdit, setToggleEdit] = useState(false)
useEffect(() => {
if (isSelected) {
trRef.current.nodes([shapeRef.current]);
trRef.current.getLayer().batchDraw();
}
}, [isSelected]);
// console.log("KText", `properties: ${JSON.stringify(properties)}`)
// console.log("KText", ` properties.text: ${properties.text}`)
const EditTextField = () => {
var textProps
const updateText = (data) => {
textProps = data
// console.log("EditTextField", `textProps: ${JSON.stringify(textProps)}`)
}
// var mAreaPos = areaPosition()
const areaPosition = () => {
let stage1 = stage.current.getStage()
return ({
x: stage1.container().offsetLeft + properties.x,
y: stage1.container().offsetTop + properties.y,
})
};
return (
<Html >
<div style={{
margin: "200px", padding: "20px", background: "lavender",
borderRadius: 20, borderStyle: "solid", borderColor: "green",
top: areaPosition().x, left: areaPosition().y
}}
onDoubleClick={() => setToggleEdit(!toggleEdit)}>
<label htmlFor="inputText">Please enter some text below:</label><p>
<textarea onChange={(evt) => (updateText({ text: evt.target.value, id: id }))}
id="inputText" name="inputText" rows="4" cols="50" placeholder="Please enter here" />
<br />
<button type="text" onClick={() => {
setToggleEdit(!toggleEdit)
setActiveText(textProps)
}}>Close</button>
</p>
</div>{/* */}
</Html >
)
}
const MainText = () => {
return (
<>
<Fragment>
<Text
stroke={"black"}
strokeWidth={1}
onTap={onSelect}
onClick={onSelect}
onDblClick={() => setToggleEdit(!toggleEdit)}
ref={shapeRef}
// {...shapeProps}
name="text"
x={properties.x}
y={properties.y}
text={properties.text}
fontFamily={properties.fontFamily}//"Serif"
fontSize={properties.fontSize}//50
fontWeight={properties.fontWeight} //"bold"
fillLinearGradientStartPoint={{ x: 0, y: 0 }}
fillLinearGradientEndPoint={{ x: 100, y: 100 }}
fillLinearGradientColorStops={[
0,
"rgba(0,0,0,0.7)",
1,
"rgba(255,155,255,0.5)"
]}
fillPriority={"linear-gradient"}
draggable
onDragEnd={e => {
/* onChange({
...shapeProps,
x: e.target.x(),
y: e.target.y(),
});*/
}}
onTransformEnd={e => {
// transformer is changing scale
/* const node = shapeRef.current;
const scaleX = node.scaleX();
const scaleY = node.scaleY();
node.scaleX(1);
node.scaleY(1);
onChange({
...shapeProps,
x: node.x(),
y: node.y(),
width: node.width() * scaleX,
height: node.height() * scaleY,
}); */
}}
/>
{isSelected && <Transformer ref={trRef} />}
</Fragment>
</>
)
}
const RenderThis = () => {
let inText = "" + properties.text
if (inText.trim().length === 0 || toggleEdit) {
return (
<EditTextField />
)
} else return (
<MainText />
)
}
// rendering function
return (
<RenderThis />
);
};
export default KText;

Related

input onChange not triggered on file submit in React

I have an input component in React that is supposed to trigger a custom handleSubmit function once a user uploads a file:
function PhotoInput({ enableInput }) {
const inputStyle = {
display: 'none'
};
const handleSubmit = (event) => {
console.log("Uploading file...")
// putToS3Bucket(event.target.files[0], '/upload')
};
if (enableInput) {
console.log("logged")
return (
<input
id="file-input"
type="file"
style={inputStyle}
accept="image/*"
onChange={handleSubmit}
/>
);
} else {
return null;
}
}
function PhotoPopover({ width, open, handlePhotoClickClose, enableInput, anchorRef }) {
const navigate = useNavigate();
return (
<>
<MenuPopover
open={open}
onClose={handlePhotoClickClose}
anchorEl={anchorRef.current}
sx={{ width: { width } }}
>
<label for="file-input">
<MenuItem
onClick={handlePhotoClickClose}
sx={{ typography: 'body2', py: 1, px: 2.5 }}
>
<Iconify
icon='eva:settings-2-fill'
sx={{
mr: 2,
width: 24,
height: 24
}}
/>
Change Profile Photo
</MenuItem>
</label>
<PhotoInput enableInput={enableInput} />
</MenuPopover>
</>
);
}
As you can see, this input is wrapped by a MenuItem component from Material. When I click on this MenuItem, a file uploading dialogue appears, but when I choose the file to upload, handleSubmit is not triggered (ie. I don't see "Uploading file..." in the console).
I'm really not sure why this is. I also tried it in a sandbox and it works there, but I don't see the difference between that and this code. Would someone be able to point me in the right direction?
In React, an input type="file" is always an uncontrolled component. You should use the File API to get information about the files uploaded. See The file input Tag.
function PhotoInput({ enableInput }) {
const fileInput = useRef(); /* create a ref*/
const inputStyle = {
display: 'none',
};
const handleSubmit = () => {
event.preventDefault();
/* get current files using ref */
console.log('Uploading file...', fileInput.current.files[0].name);
};
if (enableInput) {
return (
<input
id="file-input"
type="file"
style={inputStyle}
ref={fileInput} /* add ref*/
accept="image/*"
onChange={handleSubmit}
/>
);
} else {
return null;
}
}
Working example
You should change the format of the function call, i.e.:
onChange={(e) => handleSubmit(e)}

Utilise button-onClick event instead of input-onChange event to change state in React

I am currently experimenting with React and I am trying to change the background colour of a div according to what the user enters. I have created an Input-component with the input-element with a button, apart from the App-component, however I am unable to type in the input-element without the onChange-event, which I expected. I am unsure of how to change the state, of the state variable ('color' in App.js) with a button click instead of the onChange-event.
My App.js
const AppDiv = styled.div
`margin: 0;
box-sizing: border-box;
display: flex;
width: 100%;
`
class App extends Component {
state = {
color: ' ',
name: null
}
colorchange = (event) => {
event.preventDefault();
this.setState({
color: event.target.value
})
}
render(){
return (
<AppDiv>
<Input
name = {this.state.name}
colour = {this.state.color}
colourChange = {this.colorchange}
Changecolour = {this.changecolour}
/>
</AppDiv>
)
}
}
Input.js
const ColorButton = styled.button
`
width: 100px;
height: 50px;
border-radius: 24px/50%;
background-color: green;
margin: 10px;
`
const ColorDiv = styled.div
`
height: 100vh;
flex-basis: 300px;
background-color: ${props => props.colour}; //Changing background with 'colour' prop
`
const input = (props) => {
return (
<ColorDiv>
<h2>What is your name</h2>
<input type = "text" value = {props.name}/>
<h2>Choose your colour</h2>
<input type = "text" value = {props.colour} />
<ColorButton onClick = {props.colourChange}> Change Colour </ColorButton>
</ColorDiv>
)
}
I am using styled-components to apply styling. Any suggestions or help will be appreciated. Thank you
The best way to achieve what you are doing is to have 2 pieces of state. One for the text you have inputted in the <input /> and one for the color you want the background to be.
In the App
state = {
colorText: null,
backgroundColor: null,
}
onTextChange = (e) => {
this.setState({
colorText: event.target.value
})
}
onUpdateBackgroundColor = () => {
this.setState({
backgroundColor: this.state.colorText
})
}
.......
<Input
colour = {this.state.colorText}
backgroundColor={this.state.backgroundColor}
onTextChange = {this.onTextChange}
updateBackgroundColor = {this.onUpdateBackgroundColor}
/>
then in Input.tsx
background-color: ${props => props.backgroundColor};
<input type="text" value={props.colour} onChange={props.onTextChange} />
<ColorButton onClick={props.updateBackgroundColor}> Change Colour </ColorButton>
Note: To make this better, you should move the state into Input as keeping state in a parent component and updating it on every input change will cause a lot of renders
You can either user a ref for you input and not having it controlled
export default function App() {
const [color, setColor] = React.useState("");
const inputRef = React.useRef();
return (
<div className="App">
<h1>Your Color: {color}</h1>
<input ref={inputRef} />
<button
onClick={() => {
setColor(inputRef.current.value);
}}
>
Update Color
</button>
</div>
);
}
Or having separated state for your input:
export default function App() {
const [color, setColor] = React.useState("");
const [inputValue, setInputValue] = React.useState("");
return (
<div className="App">
<h1>Your Color: {color}</h1>
<input
value={inputValue}
onChange={({ target: { value } }) => {
setInputValue(value);
}}
/>
<button
onClick={() => {
setColor(inputValue);
}}
>
Update Color
</button>
</div>
);
}

How change the size of UI Chip dynamically

In the following example, I am trying to change the size the UI chip in dynamically in order to respond to the font size of its parents using the em css unit.
My goal: I want to do something like this
style={{size:'1em'}}
My problem: the chip element in material-ui is not resizable like most of the material-UI components.
I tried:
style={{transform:'scale(1em)'}} but it did not work at all. I don't know how to change the anchor point of transform.
I also tried to create my own chip with <img style={{float: 'left', width: '1em', borderRadius: '50%',}}/> but it does not look original as the material UI chip.
import Avatar from '#material-ui/core/Avatar'
import Chip from '#material-ui/core/Chip'
function Chips() {
const classes = useStyles()
const handleDelete = () => {
console.info('You clicked the delete icon.')
}
const handleClick = () => {
console.info('You clicked the Chip.')
}
return (
<div className={classes.root}>
<h1>
<Chip
//style={{size:'1em'}}
avatar={<Avatar alt="Natacha" src="/static/images/avatar/1.jpg" />}
label="Deletable"
onDelete={handleDelete}
/>
</h1>
<h4>
<Chip
//style={{size:'1em'}}
avatar={<Avatar alt="Natacha" src="/static/images/avatar/1.jpg" />}
label="Deletable"
onDelete={handleDelete}
/>
</h4>
</div>
)
}
currently, Chip doesn't support the prop for size (Hope they support it in the future 🤞).
For this, you've to make your own custom component. I've created one name CustomChip.
In this, pass a prop named size, and the rest of the sizes of the chip scales accordingly.
CustomChip.js
function CustomChip(props) {
const { size = 1, ...restProps } = props;
const classes = useStyles({ size });
return (
<Chip
className={classes.root}
classes={{ avatar: classes.avatar, deleteIcon: classes.deleteIcon }}
{...restProps}
/>
);
}
const useStyles = makeStyles((theme) => ({
root: {
fontSize: (props) => `${props.size * 0.8125}rem`,
height: (props) => `${props.size * 32}px`,
borderRadius: "9999px"
},
avatar: {
"&&": {
height: (props) => `${props.size * 24}px`,
width: (props) => `${props.size * 24}px`,
fontSize: (props) => `${props.size * 0.75}rem`
}
},
deleteIcon: {
height: (props) => `${props.size * 22}px`,
width: (props) => `${props.size * 22}px`,
color: "green"
}
}));
Here the provided size gets multiplied by the default sizes of the parts.
use:-
<CustomChip
size={2}
avatar={<Avatar alt="Natacha" src="/static/images/avatar/1.jpg" />}
label="Deletable"
onDelete={handleDelete}
/>
Working sandbox link:-

Dynamically disable button when a variable reachs a value

I would like, as I've said on the title, change the state of my button "Annuler" when I reach a certain value of "tab" variable. I obviously verified if my problem weren't similar to another person but I didn' found at all. I also tried to use an arrow function in order to return the value of true or false for some conditions. Also tried with the code below by using "something ? true : false" format. I must necessarily use a function instead of class. Here's my code :
import { StyleSheet , View , ScrollView , Image , Text , FlatList , TouchableHighlight, StatusBar, Button, Alert} from 'react-native';
import * as Font from 'expo-font';
import Header from './components/Header';
import Footer from './components/Footer';
import { AppLoading } from 'expo';
import modulesData from './Helpers/modulesData.js';
export default function App() {
...
var tab = 0;
var arr = Array(3);
var arrstring = arr.toString()
var i = 0
var classModules = (module) => {
arr[tab] = module;
arr.length = 3;
tab++;
if (tab == 3){
var arrstring = arr.toString()
Alert.alert("Les modules sélectionnés sont :",arrstring);
}
}
var unfillarray = () => {
if (tab => 3)
{
arr = ['','','']
tab = 0;
}
}
var disablebutton = false;
if(fontsLoaded){
return (
...
<FlatList contentContainerStyle={styles.Flatlist}
data={modulesData}
keyExtractor={(item) => item.id.toString()}
renderItem={({item}) =>
<TouchableHighlight style={styles.DivModule}
onLongPress={() => Alert.alert("Description du "+modulesData[item.id - 1].title+" : ",modulesData[item.id - 1].overview)}
onPress={() => {classModules(modulesData[item.id -1].title)}}
underlayColor="#D8DFE3">
<Text style={{fontSize:16}}>{item.title}</Text>
</TouchableHighlight>
}
/>
<Button title="Annuler" style={{height:50}} color="#FFD48E" onPress={() => {unfillarray()}} disabled={tab == 3 ? false : true }>
<Text style={{color:"black"}}>
Annuler
</Text>
</Button>
</View>
);
} else {
return (
...
);
}
}
const styles = StyleSheet.create({
HeaderInfoText:{
...
},
HeaderInfo:{
...
},
Flatlist:{
...
},
DivModule:{
...
}
})```
I think you need to play with the state like this call this function or simply update state:
this.state{
isDisabled: false
}
handleChange = () => {
this.setState({ isDisabled: !this.state.isDisabled})
}

ReactGridLayout.children[0].y must be a number

I get the following error message when I'm trying to run the website in my development environment:
Uncaught Error: ReactGridLayout:
ReactGridLayout.children[0].y must be a number!
at validateLayout (app.js:6171)
at app.js:6132
at forEachSingleChild (app.js:62734)
at traverseAllChildrenImpl (app.js:62638)
at traverseAllChildrenImpl (app.js:62654)
at traverseAllChildren (app.js:62709)
at Object.forEachChildren [as forEach] (app.js:62754)
at synchronizeLayoutWithChildren (app.js:6117)
at ReactGridLayout._initialiseProps (app.js:40638)
at new ReactGridLayout (app.js:40089)
There is also an error telling me this:
app.js:77841 The above error occurred in the component:
in ReactGridLayout (created by ResponsiveReactGridLayout)
in ResponsiveReactGridLayout (created by WidthProvider)
in WidthProvider (created by Grid)
in div (created by Grid)
in Grid (created by Test)
in Test
This is my Test.js file:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import '../../../public/css/app.css';
import '../../../public/css/all.css';
import Grid from '../components/Grid';
class Test extends Component{
render() {
return (
<Grid/>
)
}
}
export default Test;
if (document.getElementById('example')) {
ReactDOM.render(<Test />, document.getElementById('example'));
}
This is my Grid.jsx file:
import '../../../public/css/all.css';
import React from 'react';
import _ from "lodash";
import {WidthProvider, Responsive} from 'react-grid-layout';
import Select from 'react-select';
import 'react-select/dist/react-select.css';
import Clock from './Clock.jsx';
import Weather from './Weather.jsx';
const ResponsiveReactGridLayout = WidthProvider(Responsive);
const originalLayouts = getFromLS("layouts") || [];
/* This class generates the layout for the web app. It renders the grid
* and it's items, but also button's and a dropdown menu, to control the grid.
*/
class Grid extends React.PureComponent {
static defaultProps = {
className: "layout",
cols: { lg: 12, md: 10, sm: 6, xs: 4, xxs: 2},
rowHeight: 100,
autoSize: true,
};
constructor(props) {
super(props);
this.state = {
items: originalLayouts.map(function(i, key, list) {
return {
i: originalLayouts[key].i,
x: originalLayouts[key].x,
y: originalLayouts[key].y,
w: originalLayouts[key].w,
h: originalLayouts[key].h,
widget: originalLayouts[key].widget,
minW: originalLayouts[key].minW,
minH: originalLayouts[key].minH,
maxH: originalLayouts[key].maxH
};
}),
selectedOption: '',
newCounter: originalLayouts.length
};
this.onAddItem = this.onAddItem.bind(this);
this.onBreakPointChange = this.onBreakPointChange.bind(this);
this.onLayoutChange = this.onLayoutChange.bind(this);
this.onLayoutReset = this.onLayoutReset.bind(this);
}
/* This function renders all grid items in the layout array. It creates a div
* with a remove button, and content. The content managed by a switch statement,
* which output is based on the widget property from the grid items.
*/
createElement(el) {
const removeStyle = {
position: 'absolute',
right: '2px',
top: 0,
cursor: 'pointer'
};
const i = el.i;
const widget = el.widget;
return (
<div key={i} data-grid={el}>
{(() => {
switch(widget) {
case 'Clock':
return <Clock/>;
case 'Photo':
return <div className='photo'></div>;
case 'Weather':
return <Weather/>;
default:
return <span>{widget}</span>;
}
})()}
<span
className='remove'
style={removeStyle}
onClick={this.onRemoveItem.bind(this, i)} >
x
</span>
</div>
);
}
/* The onAddItem() function is called when the user clicks on the 'Add Item' button.
* It adds a new grid item to the state, and takes the selected item in the dropmenu
* into account. This way the correct widget is loaded by the createElement() function.
*/
onAddItem() {
var selection = this.state.selectedOption ? this.state.selectedOption : 0;
var widgetProps = returnProps(selection.value);
if(selection) {
console.log('adding', 'n' + this.state.newCounter + '; ' + selection.value);
} else {
console.log('adding', 'n' + this.state.newCounter + '; empty');
}
this.setState({
items: this.state.items.concat({
i: 'n' + this.state.newCounter,
x: (this.state.items.length * 2) % (this.state.cols || 12),
y: Infinity,
w: widgetProps.w,
h: widgetProps.h,
widget: selection ? selection.value : '',
minW: widgetProps.minW,
minH: widgetProps.minH,
maxH: widgetProps.maxH,
}),
newCounter: this.state.newCounter + 1
});
}
/* onLayoutReset() is called when the user clicks on the 'Reset Layout' button.
* It clears the localStorage and then issues a window refresh.
*/
onLayoutReset() {
localStorage.clear();
window.location.reload();
}
/* Calls back with breakpoint and new # cols */
onBreakPointChange(breakpoint, cols) {
this.setState({
breakpoint: breakpoint,
cols: cols
});
}
/* Is called whenever the layout is changed. The for loop adds widget attribute
* from items array to objects in layout array, so that the widget props
* are also saved to localStorage. This is because objects in the layout array
* do not include a widget property by default.
*/
onLayoutChange(layout) {
this.setState({ layout: layout });
for (var i = 0; i < this.state.items.length; i++) {
layout[i].widget = this.state.items[i].widget;
}
saveToLS('layouts', layout);
}
/* When a user presses the little 'x' in the top right corner of a grid item,
* this function is called. It removes the corresponding grid item.
*/
onRemoveItem(i) {
this.setState({ items: _.reject(this.state.items, {i: i }) });
}
/* handleChange passes the selected dropdown item to the state. */
handleChange = (selectedOption) => {
this.setState({ selectedOption });
if (selectedOption) {
console.log(`Selected: ${selectedOption.label}`);
}
};
/* This render function, renders the grid, dropdown-menu, 'Add Item'-button
* and 'Reset Layout'-button. This is also where the createElement() function
* is called for each grid item.
*/
render() {
const { selectedOption } = this.state;
return (
<div>
<div className='widgetselecter'>
<Select className='dropdown'
name="form-field-name"
value={selectedOption}
onChange={this.handleChange}
options={[
{ value: 'one', label: 'One' },
{ value: 'Clock', label: 'Clock' },
{ value: 'Photo', label: 'Photo' },
{ value: 'Weather', label: 'Weather' },
]}
/>
<button className='addButton' onClick={this.onAddItem}>Add Item</button>
<button className='reset' onClick={this.onLayoutReset}>Reset Layout</button>
<span className='title'>/Dash</span>
</div>
<ResponsiveReactGridLayout
onLayoutChange={this.onLayoutChange}
onBreakPointChange={this.onBreakPointChange}
{...this.props}>
{_.map(this.state.items, el => this.createElement(el))}
</ResponsiveReactGridLayout>
</div>
);
}
}
/* Retrieve layout from local storage. */
function getFromLS(key) {
let ls = {};
if (global.localStorage) {
try {
ls = JSON.parse(global.localStorage.getItem("rgl-8")) || {};
} catch (e) {
/*Ignore*/
}
}
return ls[key];
}
/* Save layout to local storage. */
function saveToLS(key, value) {
if (global.localStorage) {
global.localStorage.setItem(
"rgl-8",
JSON.stringify({
[key]: value
})
);
}
}
/* returnProps function returns widget-specific properties like width, min width,
* heigth, etc.
*/
function returnProps(selection) {
switch(selection) {
case 'Clock':
return {
w: 1.5,
h: 1,
minW: 1.5,
minH: 1,
maxH: 1000
};
case 'Weather':
return {
w: 3,
h: 3,
minW: 3,
minH: 3,
maxH: 3
};
default:
return {
w: 2,
h: 2,
minW: 1,
minH: 1,
maxH: 1000,
};
}
}
export default Grid;
I can't remember that I changed anything in the code and I also can't find anything related to the error message on Google. Can anyone tell me more about it or explain it to me? So i can look for a solution.
Seems I had to change this bit of code:
<ResponsiveReactGridLayout
onLayoutChange={this.onLayoutChange}
onBreakPointChange={this.onBreakPointChange}
{...this.props}>
{_.map(this.state.items, el => this.createElement(el))}
>
</ResponsiveReactGridLayout>
to this:
<ResponsiveReactGridLayout
{...this.props}
onBreakpointChange={this.onBreakpointChange}
onLayoutChange={this.onLayoutChange}>
{_.map(this.state.items, el => this.createElement(el))}
</ResponsiveReactGridLayout>
I think it has something to do with the order of rules of code and then especially this part:
>
{_.map(this.state.items, el => this.createElement(el))}
because this piece is outside the <ResponsiveReactGridLayout> now. I'm not sure if this is the right solution but it works for me. So if anyone has some additional information let me know please.