Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
"react-google-recaptcha": "2.1.0",
"react-gtm-module": "2.0.11",
"react-highlight-words": "0.17.0",
"react-hook-form": "7.12.2",
"react-hook-form": "7.61.1",

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug Report 5308-004
Error occurs when adding filters to ticket list.

TicketFilter.mp4

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug Report 5308-005
The Update filter button stays disabled when updating groups smart filter's name.

GroupFilter.mp4

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug Report 5308-006
No Open Street Map option in GIS.

GIS

"react-intl": "5.21.0",
"react-is": "17.0.2",
"react-konva": "17.0.2-5",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,5 @@ export type UploadItemFields = CreateDrawingRevisionBody & {
extension: string;
isMultiPagePdf?: boolean;
};

export type UploadDrawingFormType = { uploads: UploadItemFields[] };
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const EditProfileModal = ({ open, onClickClose, initialTab }: EditProfile

const defaultPersonalValues = defaults(
pick(omitBy(user, isNull), ['firstName', 'lastName', 'email', 'company', 'countryCode']),
{ company: '', avatarFile: '' },
{ company: '', avatarFile: undefined },
);

const onTabChange = (_, selectedTab) => setActiveTab(selectedTab);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export const EditProfileAvatar = ({ user }: EditProfilePersonalTabProps) => {
/>
</UserInfo>
</Header>
{(fileSizeError || error) && <ErrorMessage title={fileSizeError || error.message} />}
{(fileSizeError || error) && <ErrorMessage title={fileSizeError || error.message?.toString()} />}
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const EditProfilePersonalTab = ({
reset,
setError: setFormError,
control,
formState: { errors: formErrors, isDirty, touchedFields, isSubmitting },
formState: { errors: formErrors, isSubmitting },
} = useFormContext();
const userIsMissingRequiredData = userHasMissingRequiredData(user);

Expand Down Expand Up @@ -107,10 +107,9 @@ export const EditProfilePersonalTab = ({
const canSubmit = isEmpty(formErrors) && fieldsAreDirty;

useEffect(() => {
if (submitWasSuccessful) {
setSubmitWasSuccessful(false);
}
}, [JSON.stringify(isDirty), touchedFields]);
if (!fieldsAreDirty || !submitWasSuccessful) return
setSubmitWasSuccessful(false);
}, [fieldsAreDirty]);

useEffect(() => {
if (userIsMissingRequiredData) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ export const CreateDrawingDialog = ({ open, onClickClose }) => {
onSubmit={handleSubmit(onSubmit)}
confirmLabel={formatMessage({ id: 'drawings.creation.ok', defaultMessage: 'Create Drawing' })}
maxWidth="sm"
{...formState}
// {...formState} Some properties in formState where
// converted from enumerable to non-enumerable isValid one of them so {...formstate} will no longer
// pass the correct values.
isValid={formState.isValid}
isSubmitting={formState.isSubmitting}
>
<DrawingForm formData={formData} />
</FormModal>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export const EditDrawingDialog = ({ open, onClickClose, drawingId }:Props) => {
onSubmit={handleSubmit(onSubmit)}
confirmLabel={formatMessage({ id: 'drawings.edit.ok', defaultMessage: 'Save Drawing' })}
maxWidth="sm"
{...formState}
isSubmitting={formState.isSubmitting}
isValid={dirtyValuesChanged(formData, drawingId) && formState.isValid}
>
{drawing.calibration.units
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

import { formatMessage } from '@/v5/services/intl';
import { FormattedMessage } from 'react-intl';
import { useFormContext } from 'react-hook-form';
import { FieldError, useFormContext } from 'react-hook-form';
import { MenuItem } from '@mui/material';
import { FormNumberField, FormSelect, FormTextField } from '@controls/inputs/formInputs.component';
import { get, isNumber } from 'lodash';
Expand All @@ -30,16 +30,17 @@ import { DoubleInputLineContainer } from '../../drawingDialogs/drawingForm.style
import { Loader } from '@/v4/routes/components/loader/loader.component';
import { DrawingRevisionsActionsDispatchers, DrawingsActionsDispatchers } from '@/v5/services/actionsDispatchers';
import { CALIBRATION_INVALID_RANGE_ERROR } from '@/v5/validation/drawingSchemes/drawingSchemes';
import { UploadDrawingFormType } from '@/v5/store/drawings/revisions/drawingRevisions.types';

export const SidebarForm = () => {
const teamspace = TeamspacesHooksSelectors.selectCurrentTeamspace();
const project = ProjectsHooksSelectors.selectCurrentProject();
const types = DrawingsHooksSelectors.selectTypes();
const { getValues, formState: { errors, dirtyFields }, trigger, watch } = useFormContext();
const { getValues, formState: { errors, dirtyFields }, trigger, watch } = useFormContext<UploadDrawingFormType>();
const { fields, selectedId } = useContext(UploadFilesContext);
// @ts-ignore
const selectedIndex = fields.findIndex(({ uploadId }) => uploadId === selectedId);
const revisionPrefix = `uploads.${selectedIndex}`;
const selectedIndex: number = fields.findIndex(({ uploadId }) => uploadId === selectedId);
const revisionPrefix = `uploads.${selectedIndex}` as `uploads.${number}`;
const [drawingId, drawingName] = getValues([`${revisionPrefix}.drawingId`, `${revisionPrefix}.drawingName`]);
const disableDrawingFields = !(drawingName && !drawingId);
const getError = (field: string) => get(errors, `${revisionPrefix}.${field}`);
Expand All @@ -48,7 +49,8 @@ export const SidebarForm = () => {
const hasPendingRevisions = !!DrawingRevisionsHooksSelectors.selectRevisionsPending(drawingId);
const drawingRevisionsArePending = !!DrawingRevisionsHooksSelectors.selectIsPending(drawingId);
const needsFetchingCalibration = hasActiveRevisions && (hasPendingRevisions || drawingRevisionsArePending) && !isNumber(verticalRange[0]);
const hideBottomExtentError = (errors.calibration?.verticalRange || []).some((e) => e.message === CALIBRATION_INVALID_RANGE_ERROR);
const hideBottomExtentError = (errors.uploads?.[selectedIndex]?.calibration?.verticalRange || [])
.some((e: FieldError) => e.message === CALIBRATION_INVALID_RANGE_ERROR);

useEffect(() => {
if (get(dirtyFields, `${revisionPrefix}.calibration.verticalRange`)?.some((v) => v)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,23 +29,21 @@ import {
} from '@/v5/services/selectorsHooks';
import { getSupportedDrawingRevisionsFileExtensions } from '@controls/fileUploader/uploadFile';
import { UploadFiles } from '@components/shared/uploadFiles/uploadFiles.component';
import { UploadFieldArray, UploadFilesContextComponent } from '@components/shared/uploadFiles/uploadFilesContext';
import { UploadFilesContextComponent } from '@components/shared/uploadFiles/uploadFilesContext';
import { SidebarForm } from './sidebarForm/sidebarForm.component';
import { IDrawing } from '@/v5/store/drawings/drawings.types';
import { DrawingRevisionsActionsDispatchers, DrawingsActionsDispatchers } from '@/v5/services/actionsDispatchers';
import { UploadList } from './uploadList/uploadList.component';
import { parseFileName, reduceFileData, isPdf, getPdfFirstPage, fileToPdf, pdfToFile } from '@components/shared/uploadFiles/uploadFiles.helpers';
import { UploadItemFields } from '@/v5/store/drawings/revisions/drawingRevisions.types';
import { formatInfoUnit } from '@/v5/helpers/intl.helper';
import { UploadDrawingFormType } from '@/v5/store/drawings/revisions/drawingRevisions.types';

const REVISION_NAME_MAX_LENGTH = 50;

type UploadModalLabelTypes = {
isUploading: boolean;
fileCount: number;
};
type FormType = UploadFieldArray<UploadItemFields>;

const uploadModalLabels = ({ isUploading, fileCount }: UploadModalLabelTypes) => (isUploading
? {
title: formatMessage({
Expand Down Expand Up @@ -94,7 +92,7 @@ export const UploadDrawingRevisionForm = ({
const [isPresetLoading, setIsPresetLoading] = useState(!!presetFile);
const [isUploading, setIsUploading] = useState<boolean>(false);

const formData = useForm<FormType>({
const formData = useForm<UploadDrawingFormType>({
mode: 'onChange',
resolver: !isUploading ? yupResolver(UploadsSchema) : undefined,
context: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,12 @@ export const UploadListItem = ({
}, [drawingId, revCodeError]);

useEffect(() => {
setValue(revisionPrefix, sanitiseDrawing(selectedDrawing));
}, [JSON.stringify(selectedDrawing)]);
for (const [key, val] of Object.entries(sanitiseDrawing(selectedDrawing))) {
setValue(`${revisionPrefix}.${key}`, val);
}

}, [JSON.stringify(selectedDrawing)]);

useEffect(() => {
if (selectedDrawing?._id) {
if (selectRevisionsPending(getState(), selectedDrawing._id)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export const NewTicketCard = () => {

const formData = useForm({
resolver: yupResolver(isLoading ? null : getTicketValidator(template)),
mode: 'onChange',
mode: 'all',
defaultValues: defaultTicket,
});

Expand Down Expand Up @@ -92,7 +92,7 @@ export const NewTicketCard = () => {
await promiseToResolve;
};

const updateUnsavedTicket = () => TicketsCardActionsDispatchers.setUnsavedTicket(formData.getValues());
const updateUnsavedTicket = () => TicketsCardActionsDispatchers.setUnsavedTicket(cloneDeep(formData.getValues()));

useEffect(() => {
if (!templateAlreadyFetched(template)) {
Expand Down Expand Up @@ -162,7 +162,6 @@ export const NewTicketCard = () => {
// Im not sure this is still needed here, because we are already depending on react-hook-form to fill the form
ticket={defaultTicket}
focusOnTitle
onPropertyBlur={updateUnsavedTicket}
/>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export const CommentBox = ({ commentId, onCancelEdit, message = '', images = [],
return { ...image, src: getTicketResourceUrl(teamspace, project, containerOrFederation, ticketId, image.id, isFederation) };
});

const { watch, reset, control } = useForm<{ message: string, images: File[] }>({
const { watch, reset, control } = useForm<{ message: string, images: string[] }>({
mode: 'all',
defaultValues: {
message: desanitiseMessage(message),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Copyright (C) 2025 3D Repo Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { useRef, useEffect } from "react";

export const useChangeAndBlur = (value, onChange, onBlur) => {
const shouldBlur = useRef(false);

useEffect(() => {
if (!shouldBlur.current) return;
shouldBlur.current = false;
onBlur?.()
}, [value, onBlur]);

return (val) => {
shouldBlur.current = true;
onChange?.(val);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -26,33 +26,34 @@ import { InputContainer, Label } from './ticketImage.styles';
import { ImagesModal } from '@components/shared/modalsDispatcher/templates/imagesModal/imagesModal.component';
import { DialogsActionsDispatchers } from '@/v5/services/actionsDispatchers';
import { useSyncProps } from '@/v5/helpers/syncProps.hooks';
import { useChangeAndBlur as useChangeAndBlur } from '../../properties.hooks';

type TicketImageProps = FormInputProps & { onImageClick: () => void; inputRef? };
export const TicketImage = ({ value, onChange, onBlur, disabled, label, helperText, onImageClick, inputRef = undefined, ...props }: TicketImageProps) => {
const imgSrc = getImgSrc(value);
const [imgInModal, setImgInModal] = useState(imgSrc);
const syncProps = useSyncProps({ images: [imgInModal] });
const onChangeAndBlur = useChangeAndBlur(value, onChange, onBlur);

const handleImageClick = () => DialogsActionsDispatchers.open(ImagesModal, {
onAddMarkup: disabled
? null
: (newValue) => onChange(newValue ? stripBase64Prefix(newValue) : null),
: (newValue) => onChangeAndBlur(newValue ? stripBase64Prefix(newValue) : null),
}, syncProps);

const onUploadNewImage = (newValue) => {
if (!newValue) {
onChange(newValue);
onChangeAndBlur(newValue);
return;
}

setImgInModal(newValue);
DialogsActionsDispatchers.open(ImagesModal, {
onClose: (newImages) => onChange(stripBase64Prefix(newImages[0])),
onClose: (newImages) => onChangeAndBlur(stripBase64Prefix(newImages[0])),
onAddMarkup: setImgInModal,
}, syncProps);
};

useEffect(() => onBlur?.(), [value]);
useEffect(() => { setImgInModal(imgSrc); }, [imgSrc]);

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import EmptyImageIcon from '@assets/icons/outlined/add_image_thin-outlined.svg';
import { uploadImages } from '@controls/fileUploader/uploadImages';
import { DrawingViewerService } from '@components/viewer/drawingViewer/drawingViewer.service';
import { ViewerCanvasesContext } from '../../../../viewerCanvases.context';
import { useChangeAndBlur } from '../properties.hooks';

const EmptyImage = ({ disabled, onClick }) => (
<EmptyImageContainer disabled={disabled} onClick={onClick}>
Expand Down Expand Up @@ -72,9 +73,10 @@ export const TicketImageList = ({ value, onChange, onBlur, disabled, label, help
const isProjectAdmin = ProjectsHooksSelectors.selectIsProjectAdmin();
const imgsSrcs = (value || []).map((img) => getImgSrc(img, imgContext));
const [imgsInModal, setImgsInModal] = useState(imgsSrcs);

const onClose = () => onChange(imgsInModal?.length ? imgsInModal.map(getImgIdFromSrc) : null);
const onDeleteImages = () => onChange(null);
const onChangeAndBlur = useChangeAndBlur(value, onChange, onBlur);

const onClose = () => onChangeAndBlur(imgsInModal?.length ? imgsInModal.map(getImgIdFromSrc) : null);
const onDeleteImages = () => onChangeAndBlur(null);
const onDeleteImage = (index) => setImgsInModal(imgsInModal.filter((img, i) => index !== i));
const onEditImage = (newValue, index) => {
if (newValue) {
Expand Down Expand Up @@ -108,15 +110,10 @@ export const TicketImageList = ({ value, onChange, onBlur, disabled, label, help
setImgsInModal(imgsInModal.concat(screenshot));
openImagesModal(displayImageIndex);
};

const upload3DScreenshot = async () => uploadScreenshot(await ViewerService.getScreenshot());
const upload2DScreenshot = async () => uploadScreenshot(await DrawingViewerService.getScreenshot());


useEffect(() => {
setImgsInModal(imgsSrcs);
onBlur?.();
}, [value]);

return (
<InputContainer disabled={disabled} ref={inputRef} {...props}>
<Label>{label}</Label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { Viewer as ViewerService } from '@/v4/services/viewer/viewer';
import ViewpointIcon from '@assets/icons/outlined/aim-outlined.svg';
import TickIcon from '@assets/icons/outlined/tick-outlined.svg';
import { stripBase64Prefix } from '@controls/fileUploader/imageFile.helper';
import { useContext, useEffect, useState } from 'react';
import { useContext, useEffect, useRef, useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { cloneDeep, isEmpty } from 'lodash';
import { getImgSrc } from '@/v5/store/tickets/tickets.helpers';
Expand All @@ -40,6 +40,7 @@ import { GroupsActionMenu } from './viewActionMenu/menus/groupsActionMenu.compon
import { ViewerInputContainer } from '../viewerInputContainer/viewerInputContainer.component';
import { useSyncProps } from '@/v5/helpers/syncProps.hooks';
import { DialogsActionsDispatchers } from '@/v5/services/actionsDispatchers';
import { useChangeAndBlur } from '../properties.hooks';

type ITicketView = {
value: Viewpoint | undefined;
Expand Down Expand Up @@ -67,12 +68,13 @@ export const TicketView = ({
const imgSrc = getImgSrc(value?.screenshot);
const [imgInModal, setImgInModal] = useState(imgSrc);
const syncProps = useSyncProps({ images: [imgInModal] });
const onChangeAndBlur = useChangeAndBlur(value, onChange, onBlur);

// Image
const handleImageClick = () => DialogsActionsDispatchers.open(ImagesModal, {
onAddMarkup: disabled
? null
: (newValue) => onChange({ ...value, screenshot: stripBase64Prefix(newValue) }),
: (newValue) => onChangeAndBlur({ ...value, screenshot: stripBase64Prefix(newValue) }),
}, syncProps);

const handleNewImageUpload = (newImage, onSave) => {
Expand All @@ -85,11 +87,11 @@ export const TicketView = ({

const onUpdateImage = (newValue) => {
if (!newValue) {
onChange({ ...value, screenshot: null });
onChangeAndBlur({ ...value, screenshot: null });
return;
}

handleNewImageUpload(newValue, (newImage) => onChange({ ...value, screenshot: newImage }));
handleNewImageUpload(newValue, (newImage) => onChangeAndBlur({ ...value, screenshot: newImage }));
};

// Viewpoint
Expand All @@ -98,18 +100,18 @@ export const TicketView = ({
const screenshot = await ViewerService.getScreenshot();
const state = await getViewerState();

handleNewImageUpload(screenshot, (newImage) => onChange({ screenshot: newImage, ...currentCameraAndClipping, state }));
handleNewImageUpload(screenshot, (newImage) => onChangeAndBlur({ screenshot: newImage, ...currentCameraAndClipping, state }));
};

// Camera
const onUpdateCamera = async () => {
const currentCameraAndClipping = await ViewerService.getViewpoint();
onChange?.({ ...value, ...currentCameraAndClipping });
onChangeAndBlur?.({ ...value, ...currentCameraAndClipping });
};

const onDeleteCamera = async () => {
const { camera, ...view } = value || {};
onChange?.(isEmpty(view) ? null : view);
onChangeAndBlur?.(isEmpty(view) ? null : view);
};

const onGoToCamera = async () => {
Expand All @@ -126,10 +128,9 @@ export const TicketView = ({
state.colored = [];
state.hidden = [];
state.transformed = [];
onChange?.({ state, ...view });
onChangeAndBlur?.({ state, ...view });
};

useEffect(() => onBlur?.(), [value]);
useEffect(() => { setImgInModal(imgSrc); }, [imgSrc]);

const onGroupsClick = () => {
Expand Down
Loading