From c864177f0a18857935f3899623ae095fe4ecb109 Mon Sep 17 00:00:00 2001 From: aorin Date: Thu, 11 Jun 2026 17:16:44 +0300 Subject: [PATCH 1/4] improve error message when uploading invalid media file --- src/components/fields/ImageArrayField.tsx | 48 +++++++++++++++-------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/src/components/fields/ImageArrayField.tsx b/src/components/fields/ImageArrayField.tsx index f7238185..50028015 100644 --- a/src/components/fields/ImageArrayField.tsx +++ b/src/components/fields/ImageArrayField.tsx @@ -11,7 +11,7 @@ import exif from "exifreader"; import { validateLatLng, wgs84Validator } from "@luomus/laji-map/lib/utils"; import moment from "moment"; import { FieldProps, JSONSchemaArray, JSONSchemaObject } from "../../types"; -import ApiClient from "../../ApiClient"; +import ApiClient, { LajiApiError } from "../../ApiClient"; import ReactContext from "../../ReactContext"; import { getTemplate } from "@rjsf/utils"; @@ -476,6 +476,18 @@ export function MediaArrayField => { + const readDateFromFile = (file: File) => { + if (file.lastModified) { + const momentDate = moment(file.lastModified); + if (momentDate.isValid()) { + const date = momentDate.format("YYYY-MM-DDTHH:mm"); + if (date) { + found.date = date; + } + } + } + }; + const {exifParsers = []} = getUiOptions(this.props.uiSchema); if (!exifParsers) return Promise.resolve({}); @@ -509,18 +521,6 @@ export function MediaArrayField { - if (file.lastModified) { - const momentDate = moment(file.lastModified); - if (momentDate.isValid()) { - const date = momentDate.format("YYYY-MM-DDTHH:mm"); - if (date) { - found.date = date; - } - } - } - }; - if ("date" in found) { try { const rawDate = tags["DateTimeOriginal"]?.description; @@ -528,16 +528,20 @@ export function MediaArrayField { + console.info("Reading EXIF data failed", e); + readDateFromFile(file); + resolve(found); }); }) ); @@ -719,7 +723,17 @@ export function MediaArrayField !tmpMedias.includes(id))}); - throw e; + + let errorMsg: string; + if (typeof e === "string") { + errorMsg = e; + } else if (e instanceof LajiApiError && e.statusCode === 400) { + errorMsg = this.props.formContext.translations["InvalidFile"]; + } else { + errorMsg = this.props.formContext.translations["RequestFailed"]; + } + + throw errorMsg; } } From e30844d07fffff2bd30d4088ffd4464426f047ce Mon Sep 17 00:00:00 2001 From: aorin Date: Thu, 11 Jun 2026 17:36:08 +0300 Subject: [PATCH 2/4] fix dismiss all button not working --- .../components/FailedBackgroundJobsPanel.tsx | 6 +++++- test/image-array.spec.ts | 15 +++++++++++++++ test/test-utils.ts | 3 ++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/components/components/FailedBackgroundJobsPanel.tsx b/src/components/components/FailedBackgroundJobsPanel.tsx index cab023cf..4ba674c7 100644 --- a/src/components/components/FailedBackgroundJobsPanel.tsx +++ b/src/components/components/FailedBackgroundJobsPanel.tsx @@ -26,6 +26,10 @@ export class FailedBackgroundJobsPanel extends React.Component { this.state = {popped: true}; } + dismissAll = () => { + this.props.formContext.services.submitHooks.removeAll(); + }; + dismissFailedJob = ({hook, running}: SubmitHook) => (e: React.MouseEvent) => { e.stopPropagation(); if (running) return; @@ -79,7 +83,7 @@ export class FailedBackgroundJobsPanel extends React.Component { if (!errors.length) return null; const footer = ( - + ); return ( diff --git a/test/image-array.spec.ts b/test/image-array.spec.ts index f8a0fd2f..3417c3c4 100644 --- a/test/image-array.spec.ts +++ b/test/image-array.spec.ts @@ -98,4 +98,19 @@ test.describe("Image array", () => { await expect(form.getImageArrayField("0").$$imgs).toHaveCount(1); await remove(); }); + + test("Dismiss all button clears error panel", async () => { + await form.setState({ schema, uiSchema, uiSchemaContext, formData: [] }); + const filePath = require("path").resolve(__dirname, "./mock/pixel.png"); + const {reject, remove} = await form.setMockResponse("/images", false); + + await imgArrayField.$dropzone.locator("input").setInputFiles(filePath); + await reject({message: "Invalid file", statusCode: 400}); + + await expect(form.failedJobs.$$errors).toHaveCount(1); + await form.failedJobs.$dismissAll.click(); + await expect(form.failedJobs.$container).not.toBeVisible(); + + await remove(); + }); }); diff --git a/test/test-utils.ts b/test/test-utils.ts index 432f0d10..f5584f5e 100644 --- a/test/test-utils.ts +++ b/test/test-utils.ts @@ -132,7 +132,8 @@ export class Form { warnings = this.createValidatorPO("warning"); failedJobs = { $container: this.$locator.locator(".laji-form-failed-jobs-list"), - $$errors: this.$locator.locator(".laji-form-failed-jobs-list .list-group-item") + $$errors: this.$locator.locator(".laji-form-failed-jobs-list .list-group-item"), + $dismissAll: this.$locator.locator(".laji-form-failed-jobs-list .panel-footer button") }; $runningJobs = this.$locator.locator(".running-jobs"); From 09cd2fa31dfd78836ff9dede508aa022e7492852 Mon Sep 17 00:00:00 2001 From: aorin Date: Fri, 12 Jun 2026 14:38:18 +0300 Subject: [PATCH 3/4] add confirmChange prop to MapField --- forms/MHL.1158.json | 1 + src/components/fields/MapField.js | 13 ++++++++++++- src/translations.json | 5 +++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/forms/MHL.1158.json b/forms/MHL.1158.json index 32c98f28..d13d122c 100644 --- a/forms/MHL.1158.json +++ b/forms/MHL.1158.json @@ -5141,6 +5141,7 @@ "latitude":"wgs84Latitude", "longitude":"wgs84Longitude" }, + "confirmChange":true, "mapOptions":{ "tileLayerName":"taustakartta", "controls":{ diff --git a/src/components/fields/MapField.js b/src/components/fields/MapField.js index 54e9db1e..3d22ad0d 100644 --- a/src/components/fields/MapField.js +++ b/src/components/fields/MapField.js @@ -2,7 +2,7 @@ import * as React from "react"; import { createPortal, findDOMNode } from "react-dom"; import * as PropTypes from "prop-types"; import { MapComponent } from "./MapArrayField"; -import { getUiOptions, isObject } from "../../utils"; +import {getUiOptions, hasData, isObject} from "../../utils"; const equals = require("deep-equal"); import Spinner from "react-spinner"; import { Affix, Button, Fullscreen } from "../components"; @@ -328,6 +328,9 @@ export default class MapField extends React.Component { }; onChange = (events) => { + const options = getUiOptions(this.props.uiSchema); + const {confirmChange} = options; + let formData; events.forEach(e => { switch (e.type) { @@ -341,6 +344,14 @@ export default class MapField extends React.Component { formData = this.getFormDataFromGeometry(undefined); } }); + + if (confirmChange && hasData(this.props.formData) && !equals(formData, this.props.formData)) { + if (!confirm(this.props.formContext.translations.ConfirmLocationChange)) { + this.map.updateDrawData(this.getDrawOptions(this.props)); + return; + } + } + this._zoomToDataOnNextTick = true; this.props.onChange(formData); }; diff --git a/src/translations.json b/src/translations.json index 09063157..afafe066 100644 --- a/src/translations.json +++ b/src/translations.json @@ -679,6 +679,11 @@ "en": "Pan the location inside the circle", "sv": "Placera platsen inne i cirkeln" }, + "confirmLocationChange": { + "fi": "Haluatko varmasti muuttaa sijaintia?", + "en": "Are you sure you want to change the location?", + "sv": "Är du säker på att du vill ändra platsen?" + }, "unknown": { "fi": "Ei tietoa", "en": "Unknown", From dc6358fd8f97b5a662a4f01a771d4bd04bb98650 Mon Sep 17 00:00:00 2001 From: aorin Date: Fri, 12 Jun 2026 14:54:28 +0300 Subject: [PATCH 4/4] fine-tuning --- src/components/fields/ImageArrayField.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/fields/ImageArrayField.tsx b/src/components/fields/ImageArrayField.tsx index 50028015..e28e5400 100644 --- a/src/components/fields/ImageArrayField.tsx +++ b/src/components/fields/ImageArrayField.tsx @@ -728,9 +728,9 @@ export function MediaArrayField