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

Support for multiple languages in project edit view #2075

Merged
merged 2 commits into from
Dec 27, 2019
Merged
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
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
110 changes: 110 additions & 0 deletions frontend/src/components/projectEdit/inputLocale.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import React, { useState, useLayoutEffect, useCallback, useContext } from 'react';
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 updateState = 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 handleChange = e => {
setValue(e.target.value);
if (props.preview !== false) {
const html = htmlFromMarkdown(e.target.value);
setPreview(html);
}
};

// Resets preview when language changes.
useLayoutEffect(() => {
setPreview(null);
}, [language]);

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]);

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"
onBlur={updateState}
className={styleClasses.inputClass}
name={props.name}
value={value}
onChange={handleChange}
/>
) : (
<textarea
className={styleClasses.inputClass}
rows={styleClasses.numRows}
type="text"
name={props.name}
value={value}
onBlur={updateState}
onChange={handleChange}
></textarea>
)}

{preview &&
<>
<h3 className="ttu f6 fw6 blue-grey mb1">Preview:</h3>
<div dangerouslySetInnerHTML={preview} className="pa2 bg-grey-light blue-dark"/>
</>
}
</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