Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include unity activity file upload component (M2-7687) #1897

Merged
merged 17 commits into from
Sep 12, 2024
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export const EditablePerformanceTasks: string[] = [
EditablePerformanceTasksType.Flanker,
EditablePerformanceTasksType.Gyroscope,
EditablePerformanceTasksType.Touch,
EditablePerformanceTasksType.Unity,
];
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export enum EditablePerformanceTasksType {
Flanker = 'flanker',
Gyroscope = 'gyroscope',
Touch = 'touch',
Unity = 'unity',
}

export const enum PerformanceTasks {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ describe('getPerformanceTaskPath', () => {
${EditablePerformanceTasksType.Flanker} | ${page.builderAppletFlanker}
${EditablePerformanceTasksType.Gyroscope} | ${page.builderAppletGyroscope}
${EditablePerformanceTasksType.Touch} | ${page.builderAppletTouch}
${EditablePerformanceTasksType.Unity} | ${page.builderAppletUnity}
`('performanceTask = $performanceTask', ({ performanceTask, expected }) => {
const result = getPerformanceTaskPath(performanceTask);
expect(result).toBe(expected);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const performanceTaskPaths: Record<EditablePerformanceTasksType, string> = {
[PerfTaskType.Flanker]: page.builderAppletFlanker,
[PerfTaskType.Gyroscope]: page.builderAppletGyroscope,
[PerfTaskType.Touch]: page.builderAppletTouch,
[PerfTaskType.Unity]: page.builderAppletUnity,
};

export const getPerformanceTaskPath = (performanceTask: EditablePerformanceTasksType) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const getPerformanceTasksMenu = (
performanceTaskName: t(getMenuItemTitle(PerformanceTasks.Unity)),
performanceTaskDesc: t('performanceTasksDesc.unity'),
performanceTaskType: PerfTaskType.Unity,
isNavigationBlocked: true,
isNavigationBlocked: false,
}),
'data-testid': 'builder-activities-add-perf-task-unity',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const DisplayModeOptions: Readonly<
[ItemResponseType.TimeRange]: undefined,
[ItemResponseType.TouchPractice]: undefined,
[ItemResponseType.TouchTest]: undefined,
[ItemResponseType.UnityFile]: undefined,
[ItemResponseType.Unity]: undefined,
[ItemResponseType.Video]: undefined,
default: undefined,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ export const getEmptyFlowItem = () => ({
conditions: getEmptyCondition(),
});

export const getUnityEmptyFileResponse = () => ({
file: '',
});

export const getEmptyAlert = ({ config, responseType, responseValues }: GetEmptyAlert) => {
const isSlider = responseType === ItemResponseType.Slider;
const alert = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,9 @@ export const defaultPhrasalTemplateConfig = {
removeBackButton: false,
type: 'phrasal_template',
};

export const defaultUnityConfig = {
removeBackButton: false,
skippableItem: false,
timer: 0,
};
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,7 @@ export const ItemSettingsGroup = ({
isAlerts &&
~ITEM_TYPES_TO_HAVE_ALERTS.indexOf(inputType as ItemResponseType) &&
inputType !== ItemResponseType.PhrasalTemplate &&
inputType !== ItemResponseType.UnityFile
inputType !== ItemResponseType.Unity
) {
const hasAlerts = event.target.checked;

Expand Down
114 changes: 114 additions & 0 deletions src/modules/Builder/features/PerformanceTasks/Unity/Unity.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { useTranslation } from 'react-i18next';
import { Box, Button } from '@mui/material';
import { useEffect, useState } from 'react';

import { StyledHeadlineLarge, StyledTitleLarge, theme } from 'shared/styles';
import { ToggleContainerUiType, ToggleItemContainer } from 'modules/Builder/components';
import { Svg } from 'shared/components';
import { useCurrentActivity, useCustomFormContext } from 'modules/Builder/hooks';

import { StyledPerformanceTaskBody } from '../PerformanceTasks.styles';
import UnityFileModal from './UnityFileModal/UnityFileModal';
import { UnityFilePreview } from './UnityFilePreview';

export const Unity = () => {
const { t } = useTranslation();
const { setValue, watch } = useCustomFormContext();
const { fieldName } = useCurrentActivity();

const [file, setFile] = useState<File | null>(null);

const urlName = `${fieldName}.items[0].config.file`;
const url = watch(urlName);

const [fileContent, setFileContent] = useState<string>(url ? url : '');
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);

const VALID_FILE_TYPES = [
'application/x-yaml',
'application/yaml',
'text/yaml',
'text/x-yaml',
'text/plain',
'application/json',
];

const isValidFile = (file !== null && VALID_FILE_TYPES.includes(file.type)) || url?.length > 0;

const dataTestid = 'builder-activity-unity';

const handleUpload = (uploadedFile: File) => {
setFile(uploadedFile);
const formFile = watch(urlName);
if (formFile === null) {
setFileContent('');

return;
}
const reader = new FileReader();
reader.onload = (e) => {
const fileContentString = e.target?.result as string;
setFileContent(fileContentString);
setValue(urlName, fileContentString);
};
reader.readAsText(uploadedFile);
};

const handleCloseModal = () => {
setIsModalOpen(false);
};

const handleOpenModal = () => {
setIsModalOpen(true);
};

useEffect(() => {
if (url && url.length > 0) {
setFileContent(url);
}
}, [url]);
ChaconC marked this conversation as resolved.
Show resolved Hide resolved

// TODO Upload a new file if already uploaded should be implemented - TASK https://mindlogger.atlassian.net/browse/M2-7779
const OpenModalButton = () =>
isValidFile ? (
<UnityFilePreview fileContent={fileContent} />
) : (
<Box sx={{ display: 'flex', justifyContent: 'flex-start' }}>
<Button
variant="text"
startIcon={<Svg id="add" width={18} height={18} />}
onClick={handleOpenModal}
sx={{ ml: theme.spacing(-1) }}
data-testid="builder-activities-add-activity"
>
{t('upload')}
</Button>
</Box>
);

return (
<Box sx={{ overflowY: 'auto' }}>
<StyledPerformanceTaskBody sx={{ p: theme.spacing(2.4, 6.4) }}>
<StyledHeadlineLarge sx={{ mb: theme.spacing(3) }}>
{t('performanceTasks.unity')}
</StyledHeadlineLarge>
<StyledTitleLarge sx={{ mb: theme.spacing(2.4) }}>
{t('unityInstructions')}
</StyledTitleLarge>

<ToggleItemContainer
uiType={ToggleContainerUiType.PerformanceTask}
title={t('unityTaskConfigurationFile')}
Content={OpenModalButton}
data-testid="builder-activity-unity-file-uploader"
/>
</StyledPerformanceTaskBody>
<UnityFileModal
dataTestid={dataTestid}
onUpload={handleUpload}
onClose={() => handleCloseModal()}
isOpen={isModalOpen}
/>
</Box>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';

import { Modal } from 'shared/components';
import { StyledModalWrapper } from 'shared/styles';

interface UnityFileModalProps {
isOpen: boolean;
onClose: () => void;
onUpload: (file: File) => void;
dataTestid: string;
}

const UnityFileModal: React.FC<UnityFileModalProps> = ({
isOpen,
onClose,
dataTestid,
onUpload,
}) => {
const { t } = useTranslation('app');

const [selectedFile, setSelectedFile] = useState<File | null>(null);

const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
setSelectedFile(file);
}
};

const handleSubmit = () => {
if (selectedFile) {
onUpload(selectedFile);
}
onClose();
};

return (
<Modal
open={isOpen}
onClose={onClose}
onSubmit={handleSubmit}
onSecondBtnSubmit={onClose}
title={t('uploadUnityConfigFile')}
buttonText={t('import')}
secondBtnText={t('cancel')}
hasSecondBtn={false}
submitBtnColor="primary"
data-testid={dataTestid}
>
<StyledModalWrapper>
<input type="file" onChange={handleFileChange} />
</StyledModalWrapper>
</Modal>
);
};

export default UnityFileModal;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './UnityFileModal';
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from 'react';

interface UnityFilePreviewProps {
fileContent: string;
}

/* TODO - Refactor UnityFilePreview component to add the possibility of editing the Unity file
* into the Builder. Tasks https://mindlogger.atlassian.net/browse/M2-7778 and https://mindlogger.atlassian.net/browse/M2-7779 */
export const UnityFilePreview: React.FC<UnityFilePreviewProps> = ({ fileContent }) => (
ChaconC marked this conversation as resolved.
Show resolved Hide resolved
<textarea value={fileContent} readOnly style={{ width: '100%', height: '300px' }} />
);
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './UnityFilePreview';
3 changes: 3 additions & 0 deletions src/modules/Builder/features/PerformanceTasks/Unity/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Unity } from './Unity';

export default Unity;
14 changes: 12 additions & 2 deletions src/modules/Builder/pages/BuilderApplet/BuilderApplet.types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Activity, ActivityFlow, Condition, Item, ScoreReport } from 'shared/state';

import { flankerItems, getABTrailsItems, getGyroscopeOrTouchItems } from './BuilderApplet.utils';
import {
flankerItems,
getABTrailsItems,
getGyroscopeOrTouchItems,
getUnityItems,
} from './BuilderApplet.utils';

export type GetSectionConditions = {
items?: Item[];
Expand All @@ -17,7 +22,12 @@ export type GetMessageItem = {
type FlankerItemsType = typeof flankerItems;
type GyroscopeOrTouchItemsType = ReturnType<typeof getGyroscopeOrTouchItems>;
type ABTrailsItemsType = ReturnType<typeof getABTrailsItems>;
export type PerformanceTaskItems = FlankerItemsType | GyroscopeOrTouchItemsType | ABTrailsItemsType;
type UnityItemsType = ReturnType<typeof getUnityItems>;
export type PerformanceTaskItems =
| FlankerItemsType
| GyroscopeOrTouchItemsType
| ABTrailsItemsType
| UnityItemsType;

export type GetActivityFlows = {
activityFlows: ActivityFlow[];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -569,12 +569,12 @@ export const getABTrailsItems = (deviceType: DeviceType) =>
},
}));

export const getUnityFileItem = (deviceType: DeviceType) => [
export const getUnityItems = (deviceType: DeviceType) => [
{
id: undefined,
key: uuidv4(),
responseType: ItemResponseType.UnityFile,
name: `${ItemResponseType.UnityFile}_${deviceType}`,
responseType: ItemResponseType.Unity,
name: `${ItemResponseType.Unity}_${deviceType}`,
question: t('unityInstructions'),
config: {
deviceType,
Expand All @@ -595,7 +595,7 @@ export const getNewPerformanceTask = ({
[PerfTaskType.Touch]: getGyroscopeOrTouchItems(GyroscopeOrTouch.Touch),
[PerfTaskType.ABTrailsMobile]: getABTrailsItems(DeviceType.Mobile),
[PerfTaskType.ABTrailsTablet]: getABTrailsItems(DeviceType.Tablet),
[PerfTaskType.Unity]: getUnityFileItem(DeviceType.Mobile),
[PerfTaskType.Unity]: getUnityItems(DeviceType.Mobile),
};

const { items, ...restPerfTaskParams } = performanceTask || {};
Expand Down
2 changes: 2 additions & 0 deletions src/modules/Builder/routes/routes.const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const Flanker = lazy(() => import('modules/Builder/features/PerformanceTasks/Fla
const GyroscopeAndTouch = lazy(
() => import('modules/Builder/features/PerformanceTasks/GyroscopeAndTouch'),
);
const Unity = lazy(() => import('modules/Builder/features/PerformanceTasks/Unity'));

export const appletRoutes = [
{ path: Path.About, Component: AboutApplet },
Expand Down Expand Up @@ -43,4 +44,5 @@ export const performanceTasksRoutes = [
props: { type: GyroscopeOrTouch.Gyroscope },
},
{ path: Path.Touch, Component: GyroscopeAndTouch, props: { type: GyroscopeOrTouch.Touch } },
{ path: Path.Unity, Component: Unity },
];
2 changes: 1 addition & 1 deletion src/modules/Builder/types/Builder.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ export type ItemResponseTypeNoPerfTasks = Exclude<
| ItemResponseType.TouchPractice
| ItemResponseType.TouchTest
| ItemResponseType.ABTrails
| ItemResponseType.UnityFile
| ItemResponseType.Unity
>;

export enum RoundTypeEnum {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export const useActivityGrid = ({

const navigateTo =
activity?.isPerformanceTask && activity?.performanceTaskType
? // Additional validation for flanker, gyroscope and touch is done in getActivityActions as these are the only editable options
? // Additional validation for flanker, gyroscope, touch and unity is done in getActivityActions as these are the only editable options
// Here it's safe to assume the task is EditablePerformanceTasksType
getPerformanceTaskPath(
activity?.performanceTaskType as unknown as EditablePerformanceTasksType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,5 +72,5 @@ export const getItemResponseTypes = (enableParagraphTextItem: boolean) => ({
icon: <Svg id="fileAlt" />,
title: ItemResponseType.PhrasalTemplate,
},
[ItemResponseType.UnityFile]: { icon: null, title: ItemResponseType.UnityFile },
[ItemResponseType.Unity]: { icon: null, title: ItemResponseType.Unity },
});
4 changes: 3 additions & 1 deletion src/resources/app-en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1025,7 +1025,7 @@
"flanker": "Simple & Choice Reaction Time Task Builder",
"gyroscope": "CST Gyroscope",
"touch": "CST Touch",
"unity": "New Unity Task (Beta)"
"unity": "Unity"
},
"performanceTasksDesc": {
"abTrails": "A/B Trails",
Expand Down Expand Up @@ -1478,6 +1478,7 @@
"underline": "Underline",
"undo": "Undo",
"unityInstructions": "Upload Configurations",
"unityTaskConfigurationFile": "Unity Task Configuration File",
"unorderedList": "Unordered list",
"unread": "Unread",
"unselected": "Unselected",
Expand All @@ -1499,6 +1500,7 @@
"uploadMdImg": "Upload Image",
"uploadSchedule": "Please upload a schedule in one of the following formats: <1>.csv, .xls, .xlsx, .ods.</1>",
"uploadTransfluent": "Please upload JPG or transfluent PNG image less than {{size}}.",
"uploadUnityConfigFile": "Upload Unity Configuration File",
"uploadVideo": "Upload Video",
"urlPlaceholder": "Type url",
"userBuilder": "{{userName}}'s Builder",
Expand Down
Loading
Loading