From 9fbfc18788bb86ed8e385d1516737d2ba7f5ee20 Mon Sep 17 00:00:00 2001 From: Wille Marcel Date: Fri, 18 Jun 2021 17:28:36 -0300 Subject: [PATCH] Add embedded RapiD editor --- .github/dependabot.yml | 64 +++++++++- backend/models/dtos/project_dto.py | 9 +- backend/models/postgis/project.py | 6 + backend/models/postgis/statuses.py | 1 + example.env | 1 + frontend/.env.expand | 1 + frontend/package.json | 5 +- frontend/src/assets/styles/_extra.scss | 29 +++++ frontend/src/components/editor.js | 2 +- frontend/src/components/formInputs.js | 6 +- .../src/components/projectEdit/messages.js | 8 ++ .../components/projectEdit/settingsForm.js | 23 ++++ frontend/src/components/rapidEditor.js | 119 ++++++++++++++++++ .../src/components/taskSelection/action.js | 36 ++++-- .../src/components/taskSelection/footer.js | 10 +- frontend/src/config/index.js | 2 + frontend/src/utils/editorsList.js | 9 +- frontend/src/utils/openEditor.js | 6 +- frontend/src/utils/tests/editorsList.test.js | 5 + frontend/src/views/projectEdit.js | 1 + frontend/yarn.lock | 62 ++++++++- migrations/versions/8a6419f289aa_.py | 40 ++++++ 22 files changed, 416 insertions(+), 29 deletions(-) create mode 100644 frontend/src/components/rapidEditor.js create mode 100644 migrations/versions/8a6419f289aa_.py diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8a723f6b50..2fde9a67fc 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,7 +4,7 @@ updates: directory: "/" schedule: interval: daily - time: "11:00" + time: "13:00" open-pull-requests-limit: 10 ignore: - dependency-name: python-dotenv @@ -30,7 +30,7 @@ updates: directory: "/frontend" schedule: interval: daily - time: "11:00" + time: "13:00" open-pull-requests-limit: 10 ignore: - dependency-name: "@testing-library/user-event" @@ -141,6 +141,16 @@ updates: - 2.4.21 - 2.4.22 - 2.4.23 + + - dependency-name: react-datepicker + versions: + - 3.7.0 + - dependency-name: reactjs-popup + versions: + - 2.0.4 + - dependency-name: ini + versions: + - 1.3.8 - dependency-name: "@formatjs/intl-relativetimeformat" versions: - 8.0.4 @@ -260,3 +270,53 @@ updates: - dependency-name: react-placeholder versions: - 4.1.0 +- package-ecosystem: pip + directory: "/" + schedule: + interval: daily + time: "13:00" + open-pull-requests-limit: 10 + ignore: + - dependency-name: python-dotenv + versions: + - 0.15.0 + - 0.16.0 + - 0.17.0 + - dependency-name: alembic + versions: + - 1.5.5 + - 1.5.6 + - 1.5.7 + - dependency-name: sentry-sdk[flask] + versions: + - 0.20.3 + - dependency-name: greenlet + versions: + - 1.0.0 + - dependency-name: gevent + versions: + - 21.1.2 + - dependency-name: oauthlib + versions: + - 3.1.0 + - dependency-name: bleach + versions: + - 3.3.0 + - dependency-name: flask-oauthlib + versions: + - 0.9.6 + - dependency-name: flask-cors + versions: + - 3.0.10 + - dependency-name: attrs + versions: + - 20.3.0 + - dependency-name: werkzeug + versions: + - 1.0.1 + - dependency-name: black + versions: + - 20.8b1 + - dependency-name: geojson + versions: + - 2.5.0 diff --git a/backend/models/dtos/project_dto.py b/backend/models/dtos/project_dto.py index 0c88cf9091..ca22f5ddd2 100644 --- a/backend/models/dtos/project_dto.py +++ b/backend/models/dtos/project_dto.py @@ -79,7 +79,8 @@ def is_known_editor(value): raise ValidationError( f"Unknown editor: {value} Valid values are {Editors.ID.name}, " f"{Editors.JOSM.name}, {Editors.POTLATCH_2.name}, " - f"{Editors.FIELD_PAPERS.name}" + f"{Editors.FIELD_PAPERS.name}, " + f"{Editors.RAPID.name} " ) @@ -206,6 +207,9 @@ class ProjectDTO(Model): imagery = StringType() josm_preset = StringType(serialized_name="josmPreset", serialize_when_none=False) id_presets = ListType(StringType, serialized_name="idPresets", default=[]) + rapid_power_user = BooleanType( + serialized_name="rapidPowerUser", default=False, required=False + ) mapping_types = ListType( StringType, serialized_name="mappingTypes", @@ -500,6 +504,9 @@ class ProjectSummary(Model): imagery = StringType() license_id = IntType(serialized_name="licenseId") id_presets = ListType(StringType, serialized_name="idPresets", default=[]) + rapid_power_user = BooleanType( + serialized_name="rapidPowerUser", default=False, required=False + ) mapping_editors = ListType( StringType, min_size=1, diff --git a/backend/models/postgis/project.py b/backend/models/postgis/project.py index 77acf89611..334fcbe648 100644 --- a/backend/models/postgis/project.py +++ b/backend/models/postgis/project.py @@ -149,6 +149,7 @@ class Project(db.Model): imagery = db.Column(db.String) josm_preset = db.Column(db.String) id_presets = db.Column(ARRAY(db.String)) + rapid_power_user = db.Column(db.Boolean, default=False) last_updated = db.Column(db.DateTime, default=timestamp) license_id = db.Column(db.Integer, db.ForeignKey("licenses.id", name="fk_licenses")) geometry = db.Column(Geometry("MULTIPOLYGON", srid=4326), nullable=False) @@ -174,6 +175,7 @@ class Project(db.Model): Editors.ID.value, Editors.JOSM.value, Editors.CUSTOM.value, + Editors.RAPID.value, ], index=True, nullable=False, @@ -184,6 +186,7 @@ class Project(db.Model): Editors.ID.value, Editors.JOSM.value, Editors.CUSTOM.value, + Editors.RAPID.value, ], index=True, nullable=False, @@ -375,6 +378,7 @@ def update(self, project_dto: ProjectDTO): self.imagery = project_dto.imagery self.josm_preset = project_dto.josm_preset self.id_presets = project_dto.id_presets + self.rapid_power_user = project_dto.rapid_power_user self.last_updated = timestamp() self.license_id = project_dto.license_id @@ -841,6 +845,7 @@ def get_project_summary(self, preferred_locale) -> ProjectSummary: summary.license_id = self.license_id summary.status = ProjectStatus(self.status).name summary.id_presets = self.id_presets + summary.rapid_power_user = self.rapid_power_user summary.imagery = self.imagery if self.organisation_id: summary.organisation = self.organisation_id @@ -1004,6 +1009,7 @@ def _get_project_and_base_dto(self): base_dto.imagery = self.imagery base_dto.josm_preset = self.josm_preset base_dto.id_presets = self.id_presets + base_dto.rapid_power_user = self.rapid_power_user base_dto.country_tag = self.country base_dto.organisation_id = self.organisation_id base_dto.license_id = self.license_id diff --git a/backend/models/postgis/statuses.py b/backend/models/postgis/statuses.py index 0fc9cc8073..acd3c9f101 100644 --- a/backend/models/postgis/statuses.py +++ b/backend/models/postgis/statuses.py @@ -108,6 +108,7 @@ class Editors(Enum): POTLATCH_2 = 2 FIELD_PAPERS = 3 CUSTOM = 4 + RAPID = 5 class TeamVisibility(Enum): diff --git a/example.env b/example.env index 94c6d8d791..f62e54a05a 100644 --- a/example.env +++ b/example.env @@ -41,6 +41,7 @@ OSM_REGISTER_URL=https://www.openstreetmap.org/user/new # You only need to modify it in case you want to direct users to map on a different OSM instance. # ID_EDITOR_URL=https://www.openstreetmap.org/edit?editor=id& # POTLATCH2_EDITOR_URL=https://www.openstreetmap.org/edit?editor=potlatch2 +# RAPID_EDITOR_URL=https://mapwith.ai/rapid # Matomo configuration. Optional, configure it in case you have a Matomo instance. # TM_MATOMO_ID="site_id" diff --git a/frontend/.env.expand b/frontend/.env.expand index 6e6225cb5f..3247d44106 100644 --- a/frontend/.env.expand +++ b/frontend/.env.expand @@ -41,3 +41,4 @@ REACT_APP_POTLATCH2_EDITOR_URL=$POTLATCH2_EDITOR_URL REACT_APP_SENTRY_FRONTEND_DSN=$TM_SENTRY_FRONTEND_DSN REACT_APP_ENVIRONMENT=$TM_ENVIRONMENT REACT_APP_TM_DEFAULT_CHANGESET_COMMENT=$TM_DEFAULT_CHANGESET_COMMENT +REACT_APP_RAPID_EDITOR_URL=$RAPID_EDITOR_URL diff --git a/frontend/package.json b/frontend/package.json index 459c2d0efd..04da898669 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -36,6 +36,7 @@ "final-form": "^4.20.2", "fromentries": "^1.3.2", "humanize-duration": "^3.27.0", + "RapiD": "facebookincubator/rapid#rapid-v1.1.4", "immutable": "^4.0.0-rc.12", "mapbox-gl": "^1.13.1", "mapbox-gl-draw-rectangle-mode": "^1.0.4", @@ -77,8 +78,8 @@ }, "scripts": { "build-locales": "combine-messages -i './src/**/messages.js' -o './src/locales/en.json'", - "copy-static": "bash -c \"if ! (test -a public/static/index.js); then cp -R node_modules/@hotosm/id/dist/* public/static/; fi\"", - "update-static": "bash -c \"cp -R node_modules/@hotosm/id/dist/* public/static/;\"", + "copy-static": "bash -c \"mkdir -p public/static/id; mkdir -p public/static/rapid; if ! (test -a public/static/id/index.js); then cp -R node_modules/@hotosm/id/dist/* public/static/id; elif ! (test -a public/static/rapid/index.js); then cp -R node_modules/RapiD/dist/* public/static/rapid; fi\"", + "update-static": "bash -c \"mkdir -p public/static/id; mkdir -p public/static/rapid; cp -R node_modules/@hotosm/id/dist/* public/static/id; cp -R node_modules/RapiD/dist/* public/static/rapid;\"", "preparation": "bash -c \"if (test -a ../tasking-manager.env); then grep -hs ^ ../tasking-manager.env .env.expand > .env; else cp .env.expand .env; fi\"", "start": "npm run preparation && npm run copy-static && react-scripts start", "build": "npm run preparation && npm run update-static && react-scripts build", diff --git a/frontend/src/assets/styles/_extra.scss b/frontend/src/assets/styles/_extra.scss index 24a2fed1d7..54322d8d73 100644 --- a/frontend/src/assets/styles/_extra.scss +++ b/frontend/src/assets/styles/_extra.scss @@ -129,6 +129,12 @@ a:active { background-color: $white; // @extend .bg-moon-gray; border-color: $red; } + + .checkbox-toggle-sm { + left: 1rem; // @extend .left-1; + background-color: $white; // @extend .bg-moon-gray; + border-color: $red; + } } .checkbox-toggle:hover { border: dashed red !important; @@ -289,3 +295,26 @@ div.messageBodyLinks { top: 2.5rem; } } + +.rapid-beta { + display: inline-flex; + justify-content: center; + align-items: center; + font-weight: bold; + color: #eee; + margin: 0 10px; + width: 1.8em; + height: 1.8em; + border: 1px solid #909; + border-radius: 5px; + background: rgb(203,16,237); + background: -webkit-gradient(linear, left bottom, left top, color-stop(6%, rgba(108,1,167,1)), color-stop(50%, rgba(203,16,237,1)), color-stop(90%, rgb(229, 140, 253)), to(rgb(201, 42, 251))); + background: -o-linear-gradient(bottom, rgba(108,1,167,1) 6%, rgba(203,16,237,1) 50%, rgb(229, 140, 253) 90%, rgb(201, 42, 251) 100%); + background: linear-gradient(0deg, rgba(108,1,167,1) 6%, rgba(203,16,237,1) 50%, rgb(229, 140, 253) 90%, rgb(201, 42, 251) 100%); + + &:before { + content: '\03b2'; + font-size: 1.2em; + vertical-align: middle; + } +} diff --git a/frontend/src/components/editor.js b/frontend/src/components/editor.js index 298f2a83b8..82606344c5 100644 --- a/frontend/src/components/editor.js +++ b/frontend/src/components/editor.js @@ -70,7 +70,7 @@ export default function Editor({ setDisable, comment, presets, imagery, gpxUrl } // setup the context iDContext .embed(true) - .assetPath('/static/') + .assetPath('/static/id/') .locale(locale) .setsDocumentTitle(false) .containerNode(document.getElementById('id-container')); diff --git a/frontend/src/components/formInputs.js b/frontend/src/components/formInputs.js index f3ea5d645e..67aa72cf79 100644 --- a/frontend/src/components/formInputs.js +++ b/frontend/src/components/formInputs.js @@ -21,7 +21,7 @@ export const RadioField = ({ name, value, className }: Object) => ( /> ); -export const SwitchToggle = ({ label, isChecked, onChange, labelPosition }: Object) => ( +export const SwitchToggle = ({ label, isChecked, onChange, labelPosition, small = false }: Object) => (
{label && labelPosition !== 'right' && {label}}
@@ -31,8 +31,8 @@ export const SwitchToggle = ({ label, isChecked, onChange, labelPosition }: Obje checked={isChecked} onChange={onChange} /> -
-
+
+
{label && labelPosition === 'right' && {label}} diff --git a/frontend/src/components/projectEdit/messages.js b/frontend/src/components/projectEdit/messages.js index 30da13633b..dd62744bd0 100644 --- a/frontend/src/components/projectEdit/messages.js +++ b/frontend/src/components/projectEdit/messages.js @@ -347,6 +347,14 @@ export default defineMessages({ defaultMessage: 'If checked, users must edit tasks at random for the initial editing stage (managers and admins are exempt).', }, + rapidPowerUser: { + id: 'projects.formInputs.rapid_power_user', + defaultMessage: 'Enable RapiD Power User Features', + }, + rapidPowerUserDescription: { + id: 'projects.formInputs.rapid_power_user.description', + defaultMessage: 'If checked, RapiD will load with the power user dialog enabled.', + }, imagery: { id: 'projects.formInputs.imagery', defaultMessage: 'Imagery', diff --git a/frontend/src/components/projectEdit/settingsForm.js b/frontend/src/components/projectEdit/settingsForm.js index ad6ea60366..20933ab42c 100644 --- a/frontend/src/components/projectEdit/settingsForm.js +++ b/frontend/src/components/projectEdit/settingsForm.js @@ -127,6 +127,29 @@ export const SettingsForm = ({ languages, defaultLocale }) => {

+ {(projectInfo.mappingEditors.includes('RAPID') || projectInfo.validationEditors.includes('RAPID')) && ( +
+
); }; diff --git a/frontend/src/components/rapidEditor.js b/frontend/src/components/rapidEditor.js new file mode 100644 index 0000000000..3e55d04068 --- /dev/null +++ b/frontend/src/components/rapidEditor.js @@ -0,0 +1,119 @@ +import React, { useEffect, useState } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import * as RapiD from 'RapiD'; +import 'RapiD/dist/iD.css'; + +import { OSM_CONSUMER_KEY, OSM_CONSUMER_SECRET, OSM_SERVER_URL } from '../config'; + +export default function RapidEditor({ setDisable, comment, presets, imagery, gpxUrl, powerUser = false }) { + const dispatch = useDispatch(); + const session = useSelector((state) => state.auth.get('session')); + const iDContext = useSelector((state) => state.editor.context); + const locale = useSelector((state) => state.preferences.locale); + const [customImageryIsSet, setCustomImageryIsSet] = useState(false); + const windowInit = typeof window !== undefined; + const customSource = + iDContext && iDContext.background() && iDContext.background().findSource('custom'); + + useEffect(() => { + if (!customImageryIsSet && imagery && customSource) { + if (imagery.startsWith('http')) { + iDContext.background().baseLayerSource(customSource.template(imagery)); + setCustomImageryIsSet(true); + // this line is needed to update the value on the custom background dialog + window.iD.prefs('background-custom-template', imagery); + } else { + const imagerySource = iDContext.background().findSource(imagery); + if (imagerySource) { + iDContext.background().baseLayerSource(imagerySource); + } + } + } + }, [customImageryIsSet, imagery, iDContext, customSource]); + + useEffect(() => { + return () => { + dispatch({ type: 'SET_VISIBILITY', isVisible: true }); + }; + // eslint-disable-next-line + }, []); + + useEffect(() => { + if (windowInit) { + dispatch({ type: 'SET_VISIBILITY', isVisible: false }); + if (iDContext === null) { + // we need to keep iD context on redux store because iD works better if + // the context is not restarted while running in the same browser session + dispatch({ type: 'SET_EDITOR', context: window.iD.coreContext()}) + // } else{ + // if (RapiDContext === null) { + // dispatch({ type: 'SET_EDITOR', context: iDContext, rapidContext: window.iD.coreRapidContext(())}) + // } + // } + } + } + }, [windowInit, iDContext, dispatch]); + + useEffect(() => { + if (iDContext && comment) { + iDContext.defaultChangesetComment(comment); + } + }, [comment, iDContext]); + + useEffect(() => { + if (session && locale && RapiD && iDContext) { + // if presets is not a populated list we need to set it as null + try { + if (presets.length) { + window.iD.presetManager.addablePresetIDs(presets); + } else { + window.iD.presetManager.addablePresetIDs(null); + } + } catch (e) { + window.iD.presetManager.addablePresetIDs(null); + } + // setup the context + iDContext + .embed(true) + .assetPath('/static/rapid/') + .locale(locale) + .setsDocumentTitle(false) + .containerNode(document.getElementById('id-container')); + // init the ui or restart if it was loaded previously + if (iDContext.ui() !== undefined) { + iDContext.reset(); + iDContext.ui().restart(); + } else { + iDContext.init(); + } + if (gpxUrl) { + iDContext.layers().layer('data').url(gpxUrl, '.gpx'); + } + + iDContext.rapidContext().showPowerUser = powerUser; + + let osm = iDContext.connection(); + const auth = { + urlroot: OSM_SERVER_URL, + oauth_consumer_key: OSM_CONSUMER_KEY, + oauth_secret: OSM_CONSUMER_SECRET, + oauth_token: session.osm_oauth_token, + oauth_token_secret: session.osm_oauth_token_secret, + }; + osm.switch(auth); + + const thereAreChanges = (changes) => + changes.modified.length || changes.created.length || changes.deleted.length; + + iDContext.history().on('change', () => { + if (thereAreChanges(iDContext.history().changes())) { + setDisable(true); + } else { + setDisable(false); + } + }); + } + }, [session, iDContext, setDisable, presets, locale, gpxUrl, powerUser]); + + return
; +} diff --git a/frontend/src/components/taskSelection/action.js b/frontend/src/components/taskSelection/action.js index 6ad8e11130..10e56a4e68 100644 --- a/frontend/src/components/taskSelection/action.js +++ b/frontend/src/components/taskSelection/action.js @@ -33,6 +33,7 @@ import { ResourcesTab } from './resourcesTab'; import { ActionTabsNav } from './actionTabsNav'; const Editor = React.lazy(() => import('../editor')); +const RapiDEditor = React.lazy(() => import('../rapidEditor')); export function TaskMapAction({ project, projectIsReady, tasks, activeTasks, action, editor }) { useSetProjectPageTitleTag(project); @@ -119,6 +120,7 @@ export function TaskMapAction({ project, projectIsReady, tasks, activeTasks, act [window.innerWidth, window.innerHeight], null, ); + if (url) { navigate(`./${url}`); } else { @@ -142,13 +144,14 @@ export function TaskMapAction({ project, projectIsReady, tasks, activeTasks, act } else { navigate(`./?editor=${arr[0].value}`); } + window.location.reload(); }; return (
- {editor === 'ID' ? ( + {['ID', 'RAPID'].includes(editor) ? ( @@ -161,13 +164,24 @@ export function TaskMapAction({ project, projectIsReady, tasks, activeTasks, act
} > - + {editor === 'ID' ? ( + + ) : ( + + )} ) : ( 0} > - {activeEditor === 'ID' && } + {(activeEditor === 'ID' || activeEditor === 'RAPID') && ( + + )} - {editor === 'ID' && ( + {(editor === 'ID' || editor === 'RAPID') && ( ( diff --git a/frontend/src/components/taskSelection/footer.js b/frontend/src/components/taskSelection/footer.js index 18cb0fce99..9840c2f732 100644 --- a/frontend/src/components/taskSelection/footer.js +++ b/frontend/src/components/taskSelection/footer.js @@ -42,7 +42,7 @@ const TaskSelectionFooter = ({ defaultUserEditor, project, tasks, taskAction, se const lockFailed = (windowObjectReference, message) => { // JOSM and iD don't open a new window - if (!['JOSM', 'ID'].includes(editor)) { + if (!['JOSM', 'ID', 'RAPID'].includes(editor)) { windowObjectReference.close(); } fetchLockedTasks(); @@ -71,8 +71,11 @@ const TaskSelectionFooter = ({ defaultUserEditor, project, tasks, taskAction, se } } let windowObjectReference; - if (!['JOSM', 'ID'].includes(editor)) { - windowObjectReference = window.open('', `TM-${project.projectId}-${selectedTasks}`); + if (!['JOSM', 'ID', 'RAPID'].includes(editor)) { + windowObjectReference = window.open( + '', + `TM-${project.projectId}-${selectedTasks}`, + ); } if (['validateSelectedTask', 'validateAnotherTask', 'validateATask'].includes(taskAction)) { const mappedTasks = selectedTasks.filter( @@ -105,6 +108,7 @@ const TaskSelectionFooter = ({ defaultUserEditor, project, tasks, taskAction, se ) .then((res) => { lockSuccess('LOCKED_FOR_MAPPING', 'map', windowObjectReference); + window.location.reload(); }) .catch((e) => lockFailed(windowObjectReference, e.message)); } diff --git a/frontend/src/config/index.js b/frontend/src/config/index.js index fb451b9dfd..1a035935bc 100644 --- a/frontend/src/config/index.js +++ b/frontend/src/config/index.js @@ -57,6 +57,8 @@ export const ID_EDITOR_URL = export const POTLATCH2_EDITOR_URL = process.env.REACT_APP_POTLATCH2_EDITOR_URL || 'https://www.openstreetmap.org/edit?editor=potlatch2'; +export const RAPID_EDITOR_URL = + process.env.REACT_APP_RAPID_EDITOR_URL || 'https://mapwith.ai/rapid'; export const TASK_COLOURS = { READY: '#fff', diff --git a/frontend/src/utils/editorsList.js b/frontend/src/utils/editorsList.js index d3410127e3..73a7a719f0 100644 --- a/frontend/src/utils/editorsList.js +++ b/frontend/src/utils/editorsList.js @@ -1,7 +1,12 @@ -import { ID_EDITOR_URL, POTLATCH2_EDITOR_URL } from '../config'; +import { ID_EDITOR_URL, POTLATCH2_EDITOR_URL, RAPID_EDITOR_URL } from '../config'; export function getEditors(filterList, customEditor) { let editors = [ + { + label: 'RapiD', + value: 'RAPID', + url: RAPID_EDITOR_URL, + }, { label: 'iD Editor', value: 'ID', @@ -24,7 +29,7 @@ export function getEditors(filterList, customEditor) { }, ]; if (filterList) { - editors = editors.filter(i => filterList.includes(i.value)); + editors = editors.filter((i) => filterList.includes(i.value)); } if (customEditor && filterList.includes('CUSTOM')) { editors.push({ label: customEditor.name, value: 'CUSTOM', url: customEditor.url }); diff --git a/frontend/src/utils/openEditor.js b/frontend/src/utils/openEditor.js index 5c8aa7a599..e490658a81 100644 --- a/frontend/src/utils/openEditor.js +++ b/frontend/src/utils/openEditor.js @@ -14,8 +14,8 @@ export function openEditor( return '?editor=JOSM'; } const { center, zoom } = getCentroidAndZoomFromSelectedTasks(tasks, selectedTasks, windowSize); - if (editor === 'ID') { - return getIdUrl(project, center, zoom, selectedTasks, '?editor=ID'); + if (['ID', 'RAPID'].includes(editor)) { + return getIdUrl(project, center, zoom, selectedTasks, ('?editor=' + editor)); } if (windowObjectReference == null || windowObjectReference.closed) { windowObjectReference = window.open('', `iD-${project}-${selectedTasks}`); @@ -76,7 +76,7 @@ export function getIdUrl(project, centroid, zoomLevel, selectedTasks, customUrl) const base = customUrl ? formatCustomUrl(customUrl) : `${ID_EDITOR_URL}`; let url = base + '#map=' + [zoomLevel, centroid[1], centroid[0]].join('/'); // the other URL params are only needed by external iD editors - if (customUrl !== '?editor=ID') { + if (!['?editor=ID','?editor=RAPID'].includes(customUrl)) { if (project.changesetComment) { url += '&comment=' + encodeURIComponent(project.changesetComment); } diff --git a/frontend/src/utils/tests/editorsList.test.js b/frontend/src/utils/tests/editorsList.test.js index 3e50076299..edc12e9024 100644 --- a/frontend/src/utils/tests/editorsList.test.js +++ b/frontend/src/utils/tests/editorsList.test.js @@ -3,6 +3,11 @@ import { getEditors } from '../editorsList'; describe('test getEditors', () => { it('without filterList and without customEditor', () => { expect(getEditors()).toStrictEqual([ + { + label: 'RapiD', + value: 'RAPID', + url: 'https://mapwith.ai/rapid', + }, { label: 'iD Editor', value: 'ID', diff --git a/frontend/src/views/projectEdit.js b/frontend/src/views/projectEdit.js index 57bca563b6..8a86ff0891 100644 --- a/frontend/src/views/projectEdit.js +++ b/frontend/src/views/projectEdit.js @@ -75,6 +75,7 @@ export default function ProjectEdit({ id }) { perTaskInstructions: '', }, ], + rapidPowerUser: false, }); const [userCanEditProject] = useEditProjectAllowed(projectInfo); const supportedLanguages = diff --git a/frontend/yarn.lock b/frontend/yarn.lock index f3a2ea9511..0c4efe8fc3 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2433,6 +2433,18 @@ geojson-precision "^1.0.0" polygon-clipping "~0.15.3" +"@ideditor/location-conflation@~0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@ideditor/location-conflation/-/location-conflation-0.8.0.tgz#a3eb656be1ffca87d247c145105387b48c4f11a8" + integrity sha512-5hwNffKTf/0Hi8QrJ5Xj6qDhQEHCu4bgCGCD0GgRaRtgaA4HPCqcID9XFxuEVlcBwi7rgwECrkG7oxLhKeBKQQ== + dependencies: + "@aitodotai/json-stringify-pretty-compact" "^1.3.0" + "@ideditor/country-coder" "^4.0.0" + "@mapbox/geojson-area" "^0.2.2" + circle-to-polygon "^2.0.2" + geojson-precision "^1.0.0" + polygon-clipping "~0.15.1" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -4007,6 +4019,38 @@ JSONStream@0.8.0: jsonparse "0.0.5" through "~2.2.7" +RapiD@facebookincubator/rapid#rapid-v1.1.4: + version "1.1.4" + resolved "https://codeload.github.com/facebookincubator/rapid/tar.gz/452fc17b37e7efd517f426eea4cc36d956aff3cd" + dependencies: + "@ideditor/country-coder" "^4.0.0" + "@ideditor/location-conflation" "~0.8.0" + "@mapbox/geojson-area" "^0.2.2" + "@mapbox/geojson-rewind" "^0.5.0" + "@mapbox/sexagesimal" "1.2.0" + "@mapbox/togeojson" "0.16.0" + "@mapbox/vector-tile" "^1.3.1" + "@turf/bbox-clip" "^6.0.0" + abortcontroller-polyfill "^1.4.0" + aes-js "^3.1.2" + alif-toolkit "^1.2.9" + core-js "^3.6.5" + diacritics "1.3.0" + fast-deep-equal "~3.1.1" + fast-json-stable-stringify "2.1.0" + lodash-es "~4.17.15" + marked "~2.0.0" + node-diff3 "~2.1.1" + osm-auth "1.1.0" + pannellum "2.5.6" + polygon-clipping "~0.15.1" + prop-types "^15.7.2" + rbush "3.0.1" + react "^17.0.1" + react-dom "^17.0.1" + whatwg-fetch "^3.4.1" + which-polygon "2.2.0" + abab@^2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" @@ -9896,6 +9940,15 @@ marked@~2.0.0: resolved "https://registry.yarnpkg.com/marked/-/marked-2.0.7.tgz#bc5b857a09071b48ce82a1f7304913a993d4b7d1" integrity sha512-BJXxkuIfJchcXOJWTT2DOL+yFWifFv2yGYOUzvXg8Qz610QKw+sHCvTMYwA+qWGhlA2uivBezChZ/pBy1tWdkQ== +martinez-polygon-clipping@0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/martinez-polygon-clipping/-/martinez-polygon-clipping-0.7.0.tgz#5ae979d4ba32c6425c8cdd422f14d54276350ab7" + integrity sha512-EBxKjlUqrVjzT1HRwJARaSwj66JZqEUl+JnqnrzHZLU4hd4XrCQWqShZx40264NR/pm5wIHRlNEaIrev44wvKA== + dependencies: + robust-predicates "^2.0.4" + splaytree "^0.1.4" + tinyqueue "^1.2.0" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -10360,6 +10413,11 @@ node-diff3@2.1.0: resolved "https://registry.yarnpkg.com/node-diff3/-/node-diff3-2.1.0.tgz#6ab59613943db919e6e4a65a077bcd8c40239caf" integrity sha512-m04x9YCoimejmtDeG33RyEiSY+o1oMqavIQC27xprayVxcdKRZyv0m5CY3yVrkhnv0Hg/vTw6AWtJ/0xmI2CLg== +node-diff3@~2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/node-diff3/-/node-diff3-2.1.2.tgz#72f6f899b608894920ccaacddcf33cc38e459bf8" + integrity sha512-0BsMH8yqNG72kqPranIv0i9B4nXyzQ22HWcea6MBJ2/jBnLSLc9+NrqzS0UFV3TRIRtUfcPR0fUbA/AwrWIzBg== + node-fetch@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" @@ -12307,7 +12365,7 @@ react-dom@^16.9.0: prop-types "^15.6.2" scheduler "^0.19.1" -react-dom@^17.0.2: +react-dom@^17.0.1, react-dom@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== @@ -12557,7 +12615,7 @@ react@^16.9.0: object-assign "^4.1.1" prop-types "^15.6.2" -react@^17.0.2: +react@^17.0.1, react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== diff --git a/migrations/versions/8a6419f289aa_.py b/migrations/versions/8a6419f289aa_.py new file mode 100644 index 0000000000..ee4ba37750 --- /dev/null +++ b/migrations/versions/8a6419f289aa_.py @@ -0,0 +1,40 @@ +"""empty message + +Revision ID: 8a6419f289aa +Revises: 2ee4bca188a9 +Create Date: 2021-06-23 17:11:22.666814 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = "8a6419f289aa" +down_revision = "2ee4bca188a9" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "projects", sa.Column("rapid_power_user", sa.Boolean(), nullable=True) + ) + op.drop_index( + "idx_task_validation_validator_status_composite", + table_name="task_invalidation_history", + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_index( + "idx_task_validation_validator_status_composite", + "task_invalidation_history", + ["invalidator_id", "is_closed"], + unique=False, + ) + op.drop_column("projects", "rapid_power_user") + # ### end Alembic commands ###