Skip to content

Commit

Permalink
Support for multiple languages in project edit view
Browse files Browse the repository at this point in the history
  • Loading branch information
JorgeMartinezG committed Dec 18, 2019
1 parent c2c0944 commit cafb475
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 60 deletions.
38 changes: 11 additions & 27 deletions frontend/src/components/projectEdit/descriptionForm.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useContext } from 'react';

import { StateContext, styleClasses } from '../../views/projectEdit';
import { InputLocale } from './inputLocale';

export const DescriptionForm = () => {
export const DescriptionForm = ({ languages }) => {
const { projectInfo, setProjectInfo } = useContext(StateContext);

const handleChange = event => {
Expand Down Expand Up @@ -59,36 +60,19 @@ export const DescriptionForm = () => {
</select>
</div>
<div className={styleClasses.divClass}>
<label className={styleClasses.labelClass}>Name of the project*</label>
<input
className={styleClasses.inputClass}
type="text"
value={projectInfo.projectInfoLocales[0].name}
name="name"
onChange={handleChange}
/>
<InputLocale languages={languages} name="name" type="text" preview={false}>
<label className={styleClasses.labelClass}>Name of the project*</label>
</InputLocale>
</div>
<div className={styleClasses.divClass}>
<label className={styleClasses.labelClass}>Short Description*</label>
<textarea
className={styleClasses.inputClass}
rows={styleClasses.numRows}
type="text"
name="shortDescription"
value={projectInfo.projectInfoLocales[0].shortDescription}
onChange={handleChange}
></textarea>
<InputLocale languages={languages} name="shortDescription">
<label className={styleClasses.labelClass}>Short Description*</label>
</InputLocale>
</div>
<div className={styleClasses.divClass}>
<label className={styleClasses.labelClass}>Description*</label>
<textarea
className={styleClasses.inputClass}
rows={styleClasses.numRows}
type="text"
name="description"
value={projectInfo.projectInfoLocales[0].description}
onChange={handleChange}
></textarea>
<InputLocale languages={languages} name="description">
<label className={styleClasses.labelClass}>Description*</label>
</InputLocale>
</div>
</div>
);
Expand Down
100 changes: 100 additions & 0 deletions frontend/src/components/projectEdit/inputLocale.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React, { useState, useLayoutEffect, useCallback, useContext } from 'react';
import { Button } from '../../components/button';
import { htmlFromMarkdown } from '../../utils/htmlFromMarkdown';
import { StateContext, styleClasses } from '../../views/projectEdit';

export const InputLocale = props => {
const { projectInfo, setProjectInfo } = useContext(StateContext);
const [language, setLanguage] = useState(null);
const [value, setValue] = useState('');
const [preview, setPreview] = useState(null);

const locales = projectInfo.projectInfoLocales;
const handleChange = e => {
let selected = locales.filter(f => f.locale === language);
let data = null;
if (selected.length === 0) {
data = { locale: language, [e.target.name]: e.target.value };
} else {
data = selected[0];
data[e.target.name] = e.target.value;
}
// create element with new locale.
let newLocales = locales.filter(f => f.locale !== language);
newLocales.push(data);
setProjectInfo({ ...projectInfo, projectInfoLocales: newLocales });
};

const getValue = useCallback(() => {
const data = locales.filter(l => l.locale === language);
if (data.length > 0) {
return data[0][props.name];
} else {
return '';
}
}, [language, locales, props.name]);

useLayoutEffect(() => {
if (language === null) {
if (projectInfo.defaultLocale) {
setLanguage(projectInfo.defaultLocale);
}
}
}, [projectInfo, language]);

useLayoutEffect(() => {
const fieldValue = getValue();
setValue(fieldValue);
}, [getValue]);

const renderMarkdown = value => {
const html = htmlFromMarkdown(value);
setPreview(html);
};

return (
<div>
<ul className="list mb4 pa0 w-100 flex flex-wrap ttu">
{props.languages === null
? null
: props.languages.map(l => (
<li
onClick={() => setLanguage(l.code)}
className={
(l.code !== language ? 'bg-white blue-dark' : 'bg-blue-dark white') +
' ph2 mb2 pv1 f7 mr2 pointer'
}
>
{l.code}
</li>
))}
</ul>
{props.children}
{props.type === 'text' ? (
<input
type="text"
className={styleClasses.inputClass}
name={props.name}
value={value}
onChange={handleChange}
/>
) : (
<textarea
className={styleClasses.inputClass}
rows={styleClasses.numRows}
type="text"
name={props.name}
value={value}
onChange={handleChange}
></textarea>
)}
{props.preview !== false ? (
<Button onClick={() => renderMarkdown(value)} className="bg-blue-dark white mr1 f6 pv2 ttc">
preview
</Button>
) : null}

{preview ? <div dangerouslySetInnerHTML={preview} /> : null}
</div>
);
};
27 changes: 8 additions & 19 deletions frontend/src/components/projectEdit/instructionsForm.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useContext } from 'react';

import { StateContext, styleClasses } from '../../views/projectEdit';
import { InputLocale } from './inputLocale';

export const InstructionsForm = () => {
export const InstructionsForm = ({ languages }) => {
const { projectInfo, setProjectInfo } = useContext(StateContext);
const handleChange = event => {
const localesFields = ['instructions', 'perTaskInstructions'];
Expand Down Expand Up @@ -52,26 +53,14 @@ export const InstructionsForm = () => {
</p>
</div>
<div className={styleClasses.divClass}>
<label className={styleClasses.labelClass}>Detailed Instructions *</label>
<textarea
className={styleClasses.inputClass}
rows={styleClasses.numRows}
type="text"
name="instructions"
value={projectInfo.projectInfoLocales[0].instructions}
onChange={handleChange}
></textarea>
<InputLocale languages={languages} name="instructions">
<label className={styleClasses.labelClass}>Detailed Instructions *</label>
</InputLocale>
</div>
<div className={styleClasses.divClass}>
<label className={styleClasses.labelClass}>Per task instructions</label>
<textarea
className={styleClasses.inputClass}
rows={styleClasses.numRows}
type="text"
value={projectInfo.projectInfoLocales[0].perTaskInstructions}
name="perTaskInstructions"
onChange={handleChange}
></textarea>
<InputLocale languages={languages} name="perTaskInstructions">
<label className={styleClasses.labelClass}>Per task instructions</label>
</InputLocale>
<p className={styleClasses.pClass}>
Put here anything that can be useful to users while taking a task. {'{x}'}, {'{y}'} and{' '}
{'{z}'}
Expand Down
16 changes: 15 additions & 1 deletion frontend/src/components/projectEdit/settingsForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, { useContext } from 'react';
import { getEditors } from '../../utils/editorsList';
import { StateContext, styleClasses, handleCheckButton } from '../../views/projectEdit';

export const SettingsForm = () => {
export const SettingsForm = ({ languages, defaultLocale }) => {
const { projectInfo, setProjectInfo } = useContext(StateContext);

const handleMappingEditors = event => {
Expand All @@ -18,9 +18,23 @@ export const SettingsForm = () => {
setProjectInfo({ ...projectInfo, validationEditors: editors });
};

const updateDefaultLocale = event => {
setProjectInfo({ ...projectInfo, defaultLocale: event.target.value });
};

const editors = getEditors();
return (
<div className="w-100">
<div className={styleClasses.divClass}>
<label className={styleClasses.labelClass}>Default Language</label>
<select name="defaultLocale" onChange={updateDefaultLocale} className="pa2">
{languages.map(l => (
<option selected={l.code === defaultLocale ? true : false} value={l.code}>
{l.language} ({l.code})
</option>
))}
</select>
</div>
<div className={styleClasses.divClass}>
<label className={styleClasses.labelClass}>Editors for mapping</label>
{editors.map(elm => (
Expand Down
55 changes: 42 additions & 13 deletions frontend/src/views/projectEdit.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,14 @@ import { SettingsForm } from '../components/projectEdit/settingsForm';
import { ActionsForm } from '../components/projectEdit/actionsForm';
import { Button } from '../components/button';
import { API_URL } from '../config';
import { pushToLocalJSONAPI } from '../network/genericJSONRequest';

export const StateContext = React.createContext();

export const styleClasses = {
divClass: 'w-70 pb5 mb4 bb b--grey-light',
labelClass: 'f4 barlow-condensed db mb3',
pClass: 'db mb3 f5',
inputClass: 'w-80 pa2',
inputClass: 'w-80 pa2 db mb2',
numRows: '4',
buttonClass: 'bg-blue-dark dib white',
modalTitleClass: 'f3 barlow-condensed pb3 bb',
Expand All @@ -44,6 +43,7 @@ export function ProjectEdit({ id }) {
const token = useSelector(state => state.auth.get('token'));
const [error, setError] = useState(null);
const [success, setSuccess] = useState(false);
const [languages, setLanguages] = useState(null);
const [option, setOption] = useState('description');
const [projectInfo, setProjectInfo] = useState({
mappingTypes: [],
Expand All @@ -61,6 +61,15 @@ export function ProjectEdit({ id }) {
],
});

useLayoutEffect(() => {
async function getSupportedLanguages() {
const res = await fetch(`${API_URL}system/languages/`);
let resp_json = await res.json();
setLanguages(resp_json.supportedLanguages);
}
getSupportedLanguages();
}, []);

useLayoutEffect(() => {
async function fetchData() {
const res = await fetch(`${API_URL}projects/${id}/`);
Expand All @@ -69,6 +78,7 @@ export function ProjectEdit({ id }) {
resp_json = { ...resp_json, projectInfoLocales: array };
setProjectInfo(resp_json);
}

fetchData();
}, [id]);

Expand Down Expand Up @@ -112,17 +122,17 @@ export function ProjectEdit({ id }) {
const renderForm = option => {
switch (option) {
case 'description':
return <DescriptionForm />;
return <DescriptionForm languages={languages} />;
case 'instructions':
return <InstructionsForm />;
return <InstructionsForm languages={languages} />;
case 'metadata':
return <MetadataForm />;
case 'imagery':
return <ImageryForm />;
case 'permissions':
return <PermissionsForm />;
case 'settings':
return <SettingsForm />;
return <SettingsForm languages={languages} defaultLocale={projectInfo.defaultLocale} />;
case 'priority_areas':
return <PriorityAreasForm />;
case 'actions':
Expand All @@ -133,9 +143,29 @@ export function ProjectEdit({ id }) {
};

const saveChanges = () => {
pushToLocalJSONAPI(`projects/${id}/`, JSON.stringify(projectInfo), token, 'PATCH')
.then(res => setSuccess(true))
.catch(e => setError(e));
const updateProject = async () => {
const url = `${API_URL}projects/${id}/`;
const headers = {
'Content-Type': 'application/json',
'Accept-Language': 'en',
Authorization: `Token ${token}`,
};

const options = {
method: 'PATCH',
headers: headers,
body: JSON.stringify(projectInfo),
};

const res = await fetch(url, options);
if (res.status !== 200) {
setError(true);
} else {
setSuccess(true);
}
};

updateProject();
};

return (
Expand All @@ -146,14 +176,13 @@ export function ProjectEdit({ id }) {
<Button onClick={saveChanges} className="bg-red white">
Save
</Button>
<Button
onClick={() => navigate(`/projects/${id}`)}
className="bg-white blue-dark ml2"
>
<Button onClick={() => navigate(`/projects/${id}`)} className="bg-white blue-dark ml2">
Go to project page
</Button>
<p className="pt2">
{success && <span className="blue-dark bg-grey-light pa2">Project updated successfully</span>}
{success && (
<span className="blue-dark bg-grey-light pa2">Project updated successfully</span>
)}
{error && <span className="bg-red white pa2">Project update failed: {error}</span>}
</p>
</div>
Expand Down

0 comments on commit cafb475

Please sign in to comment.