Ant Design Upload component rescales images to 200x200 [React] - reactjs

I met an issue at Ant Design <Upload /> component. When I upload an image it sends two Img typed requests at the Network console. First contains my orginal image (tested at 650x650 image size), second contains transformed image to 200x200. And here is my question: Why image is rescaled to lower resolution and how can I prevent that? I want to upload orignal images with transparency if that is required.
In my case Upload component is used to upload single image. Component is placed on modal component that is why I prepared dummyRequest.
const UploadImage = ({
defaultValue,
maxFilesAmount,
}: IUploadImageProps<any>): JSX.Element => {
const [fileList, setFileList] = useState<IUploadFile<any>[]>(
defaultValue ?? []
)
const onChange = useCallback(
(info: IUploadChangeParam<IUploadFile<unknown>>): void => {
setFileList(info.fileList)
},
[setFileList]
)
const onPreview = useCallback(async (file: IUploadFile): Promise<void> => {
let src: string | undefined = file?.url ?? file?.thumbUrl ?? ''
if (!_.isNull(src)) {
src = await convertImportImageToBase64(src)
}
const image: HTMLImageElement = new Image()
const imgWindow: Window | null = window.open(src)
image.src = src
imgWindow?.document.write(image.outerHTML)
}, [])
const dummyRequest = useCallback(({ onSuccess }: IUploadRequestOption) => {
setTimeout(() => {
//#ts-ignore
onSuccess('ok')
}, 0)
}, [])
const beforeUpload = useCallback((): false => {
//Required to ignore Ant Desing default converting uploaded image
//For example that removes transparency from png files
return false
}, [])
return (
<Upload
accept={'.jpg, .jpeg, .png'}
listType={'picture-card'}
beforeUpload={beforeUpload}
fileList={fileList}
onChange={onChange}
onPreview={onPreview}
customRequest={dummyRequest}
>
{_.size(fileList) < (maxFilesAmount ?? 1) && 'Upload'}
</Upload>
)
}
At the start I had also a problem with losing transparency at .png images. Image still was defined as png but transparent background got a white color. Used beforeUpload fix that, I not sure why but okej :).
What can I do more to fix this issue? Thanks in advance.

Related

ReactJS-Leaflet: upload csv file in leaflet

here I have a question regarding references to creating a feature in the react leaflet.
So, the feature has the following functions =
The user page when they want to upload a location is in the form of a csv file containing latitude and longitude.
When the user clicks the red button above, a popup will appear to upload the csv file.
When finished uploading the csv file, it will go directly to the location based on latitude and longitude.
So my question is, does anyone have a tutorial on how to create a csv upload button that points directly to a map with reactjs and leaflets? Thank you very much
Although you have not asked to use react-leaflet I would advise you to do so because you will end up in a mess when you will have to export the map reference to reuse it across places.
First create a button that will handle the upload of a csv file. There is a really useful guide to do so without the use of libraries like paparse although it simplifies a lot this procedure. Next you need to transform the csv columns to some form of data to use. This is also included in the guide. You end up with an array of csv columns.
Then all you need to do is to create a custom react-leaflet component to render the markers and zoom to the markers viewport.
Also you can clean the csv file once you insert a new one.
function App() {
const [csvToArray, setCsvToArray] = useState([]);
const fileReader = new FileReader();
const csvFileToArray = (string) => {
const csvHeader = string.slice(0, string.indexOf("\n")).split(",");
const csvRows = string.slice(string.indexOf("\n") + 1).split("\n");
const array = csvRows.map((i) => {
const values = i.split(",");
const obj = csvHeader.reduce((object, header, index) => {
object[header] = values[index];
return object;
}, {});
return obj;
});
setCsvToArray(array);
};
const handleOnChange = (e) => {
if (csvFileToArray.length > 0) setCsvToArray([]);
const file = e.target.files[0];
if (file) {
fileReader.onload = function (event) {
const text = event.target.result;
csvFileToArray(text);
};
fileReader.readAsText(file);
}
};
console.log(csvToArray);
return (
<>
<input
type="file"
id="csvFileInput"
accept=".csv"
onChange={handleOnChange}
/>
<Map csvToArray={csvToArray} />
</>
);
}
function RenderCsvToArray({ csvToArray }) {
const map = useMap();
useEffect(() => {
if (csvToArray.length === 0) return;
const myPoints = csvToArray.map(({ Latitude, Longitude }) => [
Latitude,
Longitude
]);
const myBounds = new L.LatLngBounds(myPoints);
map.fitBounds(myBounds);
}, [csvToArray]);
return csvToArray?.map(({ Latitude, Longitude, Title }, index) => (
<Marker key={index} icon={icon} position={[Latitude, Longitude]}>
<Popup>{Title}</Popup>
</Marker>
));
}
You can see the full implementation on the demo
I have also inlcuded two csv files to play with in the form of
Title,Latitude,Longitude
Trinity College,41.745167,-72.69263
Wesleyan University,41.55709,-72.65691
and
Group,Title,Image,Description,Address,Latitude,Longitude
a,Trinity College,https://www.edx.org/sites/default/files/trinity1.jpg,"Not in the link view website more not in the link","300 Summit St - Hartford CT 06106,41.745167,-72.69263
a,Wesleyan University,https://upload.wikimedia.org/wikipedia/commons/4/41/You_are_here_-_T-shirt.jpg,"view website",45 Wyllys Ave Middletown CT 06459,41.55709,-72.65691

Simplifying a GeoJson FeatureCollection using mapshaper causes issues

More specifically, it causes the same code to render an outline on the map, and only recognizes the last path element when using pointer events.
I simplified a continents.json file on mapshaper using their website and replaced the file in my public/data folder. Currently I'm fetching the data in one component and passing it down to another.
const WorldMapAtlas = () => {
const [continents, continents] = useState<null | FeatureCollection>(null)
useEffect(() => {
fetch('data/continents_simple.json')
.then(response => response.json())
.then((worldData) => {
let continents: FeatureCollection = (worldData as FeatureCollection)
setContinents(continents)
})
.catch(error => console.log(error))
}, [])
return (
<>
{continents &&
<div className="w-3/4" >
<TestMap projectionType={geoNaturalEarth1} GeoJson={continents} size={[928, 454]} translate={[464, 227]} />
</div>
}
</>
)
}
I then try to render it with the TestMap Component
interface props {
projectionType: () => GeoProjection;
GeoJson: FeatureCollection | null;
size: [number, number];
translate: [number, number];
}
const TestMap = (props: props) => {
const svgRef = useRef(null)
const [height, width] = props.size;
useEffect(() => {
//accesses the reference element
const svg = d3.select(svgRef.current)
//declares the geoJson, projection, and path(pathGenerator)
const geoJson = props.GeoJson
const projection = props.projectionType()
.fitSize(props.size, geoGraticule10())
.translate(props.translate)
const path = d3.geoPath().projection(projection)
//uses d3 to inject the data into the svg element
const features = svg.selectAll(".country")
.data(geoJson ? geoJson.features : [])
.enter().append("path")
//basic styling attributes
.attr("d", path)
.attr("fill", "none")
.attr("stroke", "aliceblue")
.attr("stroke-width", "0.5px")
//allows pointer events anywhere inside the path even when fill=none
.attr("pointer-events", "visibleFill")
.attr("visibility", "visible")
//sets the path element id to the continent name
.attr("id", (d) => {
return `${d.properties.continent}`
})
//selects the current continent and highlights it by increasing the stroke width
.on("mouseover", (d,i) => {
svg.select(`#${i.properties.continent}`).attr("stroke-width", "2px")
})
//deselects
.on("mouseleave", (d,i) => {
svg.select(`#${i.properties.continent}`).attr("stroke-width", "0.5px")
})
//adds the .country attribute to allow for later updates on the svg element
.attr("class", "country")
}, [geoJson]);
return (
<div className="bg-blue-400">
<svg ref={svgRef} viewBox={`0 0 ${props.size[0]} ${props.size[1]}`} />
</div>
)
}
export default TestMap
When I use the unsimplifed json file it works fine. Each country is highlighted, but the strokewidth redraw takes a while. When all I do is change the fetch in WorldMapAtlas to the simplified json, an outline appears (which does not seems to be specific to one path, only disappears when all paths elements are deleted (dev tools)), and only the last feature in the json gets highlighted no matter where the cursor is.
The red dot is where my cursor is.
I've spent a lot of time on this, and I appreciate any help I could get! Thank you
After trying out a lot of different simplify options I feel silly. All you have to do is add the gj2008 option to the output command.
gj2008 [GeoJSON] use original GeoJSON spec (not RFC 7946)
This fixed all the problems! Although I have no idea why, I'm not sure how to check the original GeoJSON spec, and I'm not sure what the differences are. Hope this helps anyone who ran into the same problem!

How to load image from blob and show Image preview

I'm letting users upload a cover image.
I'm using ant design image upload. So it provides UI and the ability to crop images by default.
onst [fileList, setFileList] = useState([]);
const onPreview = async (file) => {
let src = file.url;
if (!src) {
src = await new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(file.originFileObj);
reader.onload = () => resolve(reader.result);
});
}
const image = new Image();
image.src = src;
const imgWindow = window.open(src);
imgWindow.document.write(image.outerHTML);
};
return (
<div>
<UploadButton
fileList={fileList}
beforeUpload={() => false}
onChange={onChange}
onPreview={onPreview} aspect={2} listType="picture" />
<div/>
)
Once the cropping is done, before reaching the server, I want to give users the ability to preview the imageso they can confirm it before upload.
On successfully, uploading the image, I accessed the thumb URL to show users. It's blurry and distorted.
So I went on check the network section and I found the blob as shown in Image, which gives me a quality image that I can show in the product.
I'm new to programming. I was wondering, how to access it.
You can create a temporary image url out of a blob object.
const imageUrl = URL.createObjectURL(imageBlob) // imageBlob is of type blob

Testing icon based on dynamically imported svg in react-testing-library

It's my first question here and I've been coding for only a year so please be patient. I looked for similar problems on the website but couldn't find anything that worked for me.
I created an Icon component where I dynamically import the requested SVG.
I first used this solution which was working well but when I tried to test this component with react-testing library and snapshot I realised that the snapshot was always the span that is returned when nothing is imported. I first thought it was linked to the use of useRef() because I saw people saying refs didn't work with Jest so I changed my Icon component to be this:
const Icon: FC<IconProps> = ({ type, onClick, tooltip, className }: IconProps) => {
const [IconProps, setIconProps] = useState({
className: className || 'icon'
});
const tooltipDelay: [number, number] = [800, 0];
useEffect(() => {
setIconProps({ ...IconProps, className: className || 'icon' });
}, [className]);
const SVG = require(`../../svg/${type}.svg`).default;
const spanClassName = "svg-icon-wrapper";
if (typeof SVG !== 'undefined') {
if (tooltip) {
return (
(<Tooltip title={tooltip.title} delay={tooltip.delay ? tooltipDelay : 0}>
<i className={spanClassName}>
<SVG {...IconProps} onClick={onClick} data-testid="icon" />
</i>
</Tooltip>)
);
}
return (
<SVG {...IconProps} onClick={onClick} data-testid="icon" />
);
}
return <span className={spanClassName} data-testid="span" />;
};
export default Icon;
Here is my test
it('matches snapshot of each icon', async (done) => {
jest.useFakeTimers();
const type = 'check';
const Component = <Icon type={type} />;
const renderedComp = render(Component);
setTimeout(() => {
const { getByTestId } = renderedComp;
expect(getByTestId('icon')).toMatchSnapshot();
done();
}, 3000);
jest.runAllTimers();
});
I added timeout because I thought it might be link to the time it takes to import the SVG but nothing I tried worked.
So the main problem is:
how can I have the svg being imported for this component to return an icon (with data-testid='icon') and not a span (with data-testid='span')
Help would be much appreciated. My app works perfectly and I'm stuck with this testing for a while now.

SetState inside UseEffect causing infinite loop

export const Upload = ({ initialfileList = [] }) => {
console.log("called...")
const [files,setFiles] = useState(initialfileList)
useEffect(() => {
setFiles(initialfileList)
}, [initialfileList])
return(
.....
)
}
I will not be sending initialfileList in intial render.
so I'm trying to update the state (files) when intialfileList value Changes.
But I'm not able to get why code is going into infinite loop.. Can someone help...
EDIT:
what I'm trying to achieve is there are two uploads in the form (say upload A and upload B) and checkbox as well. when the user uploads an image (say imgc) in Upload A and hits the checkbox, I wanted img c to be autouploaded in Upload B as well..
Ah, I see. How about this?
export const Upload = ({ initialfileList = [] }) => {
const [files, setFiles] = useState(initialfileList);
useEffect(() => {
function isDifferentFiles(originFiles, newFiles) {
return originFiles.length !== newFiles.length; // or whatever logic here.
}
if (isDifferentFiles(files, initialfileList)) {
setFiles(initialfileList);
}
}, [initialfileList]);
return <div>{files.length}</div>;
};
Btw, you might need to consider move the state to parent.
It sounds like you need to lift your state up - rather than storing fileLists locally, store a single fileList in the parent component, pass that down to both Upload A and Upload B, and also pass a setFileList function (which updates the state in the parent component).
i.e.:
// parent component
const Parent = () => {
const [fileList, setFileList] = useState([])
return (
<Upload fileList={fileList} setFileList={setFileList} />
<Upload fileList={fileList} setFileList={setFileList} />
)
}
const Upload = ({fileList, setFileList}) => {
return (
<UploadComponent files={fileList} onUpload={setFileList} />
)
}
This way, either upload component being updated will update both.

Resources