From 084bc3d518c9dbc3c2ded5fef95f154a10c82af8 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Tue, 23 Jan 2024 18:49:17 +0100 Subject: [PATCH 01/19] wip: move xhr management to a module and refactor It sort of work for common cases, but edge cases needs work, specifically the login flow. --- umap/static/umap/js/modules/global.js | 3 +- umap/static/umap/js/modules/request.js | 146 +++++++++++++++++ umap/static/umap/js/umap.autocomplete.js | 24 +-- .../umap/js/umap.datalayer.permissions.js | 15 +- umap/static/umap/js/umap.forms.js | 14 +- umap/static/umap/js/umap.js | 138 +++++++--------- umap/static/umap/js/umap.layer.js | 148 ++++++++---------- umap/static/umap/js/umap.permissions.js | 35 ++--- 8 files changed, 300 insertions(+), 223 deletions(-) create mode 100644 umap/static/umap/js/modules/request.js diff --git a/umap/static/umap/js/modules/global.js b/umap/static/umap/js/modules/global.js index 51ba37d0..049a18ed 100644 --- a/umap/static/umap/js/modules/global.js +++ b/umap/static/umap/js/modules/global.js @@ -1,8 +1,9 @@ import * as L from '../../vendors/leaflet/leaflet-src.esm.js' import URLs from './urls.js' +import { Request, ServerRequest } from './request.js' // Import modules and export them to the global scope. // For the not yet module-compatible JS out there. // Copy the leaflet module, it's expected by leaflet plugins to be writeable. window.L = { ...L } -window.umap = { URLs } +window.umap = { URLs, Request, ServerRequest } diff --git a/umap/static/umap/js/modules/request.js b/umap/static/umap/js/modules/request.js new file mode 100644 index 00000000..df8c4033 --- /dev/null +++ b/umap/static/umap/js/modules/request.js @@ -0,0 +1,146 @@ +// Uses `L._`` from Leaflet.i18n which we cannot import as a module yet +import { Evented, DomUtil } from '../../vendors/leaflet/leaflet-src.esm.js' + +const BaseRequest = Evented.extend({ + _fetch: async function (method, uri, headers, data) { + const id = Math.random() + this.fire('dataloading', { id: id }) + let response + + try { + response = await fetch(uri, { + method: method, + mode: 'cors', + headers: headers, + body: data, + }) + } catch (error) { + this._onError(error) + this.fire('dataload', { id: id }) + return + } + if (!response.ok) { + this.onNok(response.status, await response.text()) + } + // TODO + // - error handling + // - UI connection / events + // - preflight mode in CORS ? + + this.fire('dataload', { id: id }) + return response + }, + + get: async function (uri, headers) { + return await this._fetch('GET', uri, headers) + }, + + post: async function (uri, headers, data) { + return await this._fetch('POST', uri, headers, data) + }, + + _onError: function (error) { + console.error(error) + this.onError(error) + }, + onError: function (error) {}, + onNok: function (status) {}, +}) + +export const Request = BaseRequest.extend({ + initialize: function (ui) { + this.ui = ui + }, + onError: function (error) { + console.error(error) + this.ui.alert({ content: L._('Problem in the response'), level: 'error' }) + }, + onNok: function (status, message) { + this.onError(message) + }, +}) + +// Adds uMap specifics to requests handling +// like logging, CSRF, etc. +export const ServerRequest = Request.extend({ + post: async function (uri, headers, data) { + const token = document.cookie.replace( + /(?:(?:^|.*;\s*)csrftoken\s*\=\s*([^;]*).*$)|^.*$/, + '$1' + ) + if (token) { + headers = headers || {} + headers['X-CSRFToken'] = token + } + const response = await Request.prototype.post.call(this, uri, headers, data) + return this._handle_json_response(response) + }, + + get: async function (uri, headers) { + const response = await Request.prototype.get.call(this, uri, headers) + return this._handle_json_response(response) + }, + + _handle_json_response: async function (response) { + try { + const data = await response.json() + this._handle_server_instructions(data) + return [data, response] + } catch (error) { + this._onError(error) + } + }, + + _handle_server_instructions: function (data) { + // In some case, the response contains instructions + if (data.redirect) { + const newPath = data.redirect + if (window.location.pathname == newPath) { + window.location.reload() // Keep the hash, so the current view + } else { + window.location = newPath + } + } else if (data.info) { + this.ui.alert({ content: data.info, level: 'info' }) + this.ui.closePanel() + } else if (data.error) { + this.ui.alert({ content: data.error, level: 'error' }) + } else if (data.html) { + const ui_options = { data } + let listen_options + this.ui.openPanel(ui_options) + } + }, + + onNok: function (status, message) { + if (status === 403) { + this.ui.alert({ + content: message || L._('Action not allowed :('), + level: 'error', + }) + } else if (status === 412) { + const msg = L._( + 'Woops! Someone else seems to have edited the data. You can save anyway, but this will erase the changes made by others.' + ) + const actions = [ + { + label: L._('Save anyway'), + callback: function () { + // TODO + delete settings.headers['If-Match'] + this._fetch(settings) + }, + }, + { + label: L._('Cancel'), + }, + ] + this.ui.alert({ + content: msg, + level: 'error', + duration: 100000, + actions: actions, + }) + } + }, +}) diff --git a/umap/static/umap/js/umap.autocomplete.js b/umap/static/umap/js/umap.autocomplete.js index 1f9b65cb..cbc43153 100644 --- a/umap/static/umap/js/umap.autocomplete.js +++ b/umap/static/umap/js/umap.autocomplete.js @@ -13,7 +13,7 @@ L.U.AutoComplete = L.Class.extend({ initialize: function (el, options) { this.el = el const ui = new L.U.UI(document.querySelector('header')) - this.xhr = new L.U.Xhr(ui) + this.server = new window.umap.ServerRequest(ui) L.setOptions(this, options) let CURRENT = null try { @@ -158,21 +158,19 @@ L.U.AutoComplete = L.Class.extend({ } }, - search: function () { - const val = this.input.value + search: async function () { + let val = this.input.value if (val.length < this.options.minChar) { this.clear() return } if (`${val}` === `${this.CACHE}`) return else this.CACHE = val - this._do_search( - val, - function (data) { - this.handleResults(data.data) - }, - this + val = val.toLowerCase() + const [{ data }, response] = await this.server.get( + `/agnocomplete/AutocompleteUser/?q=${encodeURIComponent(val)}` ) + this.handleResults(data) }, createResult: function (item) { @@ -272,14 +270,6 @@ L.U.AutoComplete.Ajax = L.U.AutoComplete.extend({ label: option.innerHTML, } }, - - _do_search: function (val, callback, context) { - val = val.toLowerCase() - this.xhr.get(`/agnocomplete/AutocompleteUser/?q=${encodeURIComponent(val)}`, { - callback: callback, - context: context || this, - }) - }, }) L.U.AutoComplete.Ajax.SelectMultiple = L.U.AutoComplete.Ajax.extend({ diff --git a/umap/static/umap/js/umap.datalayer.permissions.js b/umap/static/umap/js/umap.datalayer.permissions.js index f77e086f..f4a79e92 100644 --- a/umap/static/umap/js/umap.datalayer.permissions.js +++ b/umap/static/umap/js/umap.datalayer.permissions.js @@ -51,19 +51,14 @@ L.U.DataLayerPermissions = L.Class.extend({ pk: this.datalayer.umap_id, }) }, - save: function () { + save: async function () { if (!this.isDirty) return this.datalayer.map.continueSaving() const formData = new FormData() formData.append('edit_status', this.options.edit_status) - this.datalayer.map.post(this.getUrl(), { - data: formData, - context: this, - callback: function (data) { - this.commit() - this.isDirty = false - this.datalayer.map.continueSaving() - }, - }) + await this.datalayer.map.server.post(this.getUrl(), {}, formData) + this.commit() + this.isDirty = false + this.datalayer.map.continueSaving() }, commit: function () { diff --git a/umap/static/umap/js/umap.forms.js b/umap/static/umap/js/umap.forms.js index fabf2424..3f36b184 100644 --- a/umap/static/umap/js/umap.forms.js +++ b/umap/static/umap/js/umap.forms.js @@ -685,7 +685,7 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({ return !this.value() || this.value() === this.obj.getMap().options.default_iconUrl }, - showSymbolsTab: function () { + showSymbolsTab: async function () { this.openTab('symbols') this.searchInput = L.DomUtil.create('input', '', this.body) this.searchInput.type = 'search' @@ -695,13 +695,11 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({ if (this.pictogram_list) { this.buildSymbolsList() } else { - this.builder.map.get(this.builder.map.options.urls.pictogram_list_json, { - callback: (data) => { - this.pictogram_list = data.pictogram_list - this.buildSymbolsList() - }, - context: this, - }) + const [{ pictogram_list }, response] = await this.builder.map.server.get( + this.builder.map.options.urls.pictogram_list_json + ) + this.pictogram_list = pictogram_list + this.buildSymbolsList() } }, diff --git a/umap/static/umap/js/umap.js b/umap/static/umap/js/umap.js index bf0f5f91..f142f249 100644 --- a/umap/static/umap/js/umap.js +++ b/umap/static/umap/js/umap.js @@ -98,9 +98,12 @@ L.U.Map.include({ this.urls = new window.umap.URLs(this.options.urls) this.ui = new L.U.UI(this._container) - this.xhr = new L.U.Xhr(this.ui) - this.xhr.on('dataloading', (e) => this.fire('dataloading', e)) - this.xhr.on('dataload', (e) => this.fire('dataload', e)) + this.server = new window.umap.ServerRequest(this.ui) + this.server.on('dataloading', (e) => this.fire('dataloading', e)) + this.server.on('dataload', (e) => this.fire('dataload', e)) + this.request = new window.umap.Request(this.ui) + this.request.on('dataloading', (e) => this.fire('dataloading', e)) + this.request.on('dataload', (e) => this.fire('dataload', e)) this.initLoader() this.name = this.options.name @@ -1083,7 +1086,7 @@ L.U.Map.include({ return properties }, - saveSelf: function () { + saveSelf: async function () { const geojson = { type: 'Feature', geometry: this.geometry(), @@ -1093,64 +1096,60 @@ L.U.Map.include({ formData.append('name', this.options.name) formData.append('center', JSON.stringify(this.geometry())) formData.append('settings', JSON.stringify(geojson)) - this.post(this.urls.get('map_save', { map_id: this.options.umap_id }), { - data: formData, - context: this, - callback: function (data) { - let duration = 3000, - alert = { content: L._('Map has been saved!'), level: 'info' } - if (!this.options.umap_id) { - alert.content = L._('Congratulations, your map has been created!') - this.options.umap_id = data.id - this.permissions.setOptions(data.permissions) - this.permissions.commit() - if ( - data.permissions && - data.permissions.anonymous_edit_url && - this.options.urls.map_send_edit_link - ) { - alert.duration = Infinity - alert.content = - L._( - 'Your map has been created! As you are not logged in, here is your secret link to edit the map, please keep it safe:' - ) + `
${data.permissions.anonymous_edit_url}` + const uri = this.urls.get('map_save', { map_id: this.options.umap_id }) + const [data, response] = await this.server.post(uri, {}, formData) + let duration = 3000, + alert = { content: L._('Map has been saved!'), level: 'info' } + if (!this.options.umap_id) { + alert.content = L._('Congratulations, your map has been created!') + this.options.umap_id = data.id + this.permissions.setOptions(data.permissions) + this.permissions.commit() + if ( + data.permissions && + data.permissions.anonymous_edit_url && + this.options.urls.map_send_edit_link + ) { + alert.duration = Infinity + alert.content = + L._( + 'Your map has been created! As you are not logged in, here is your secret link to edit the map, please keep it safe:' + ) + `
${data.permissions.anonymous_edit_url}` - alert.actions = [ - { - label: L._('Send me the link'), - input: L._('Email'), - callback: this.sendEditLink, - callbackContext: this, - }, - { - label: L._('Copy link'), - callback: () => { - L.Util.copyToClipboard(data.permissions.anonymous_edit_url) - this.ui.alert({ - content: L._('Secret edit link copied to clipboard!'), - level: 'info', - }) - }, - callbackContext: this, - }, - ] - } - } else if (!this.permissions.isDirty) { - // Do not override local changes to permissions, - // but update in case some other editors changed them in the meantime. - this.permissions.setOptions(data.permissions) - this.permissions.commit() - } - // Update URL in case the name has changed. - if (history && history.pushState) - history.pushState({}, this.options.name, data.url) - else window.location = data.url - alert.content = data.info || alert.content - this.once('saved', () => this.ui.alert(alert)) - this.ui.closePanel() - this.permissions.save() - }, - }) + alert.actions = [ + { + label: L._('Send me the link'), + input: L._('Email'), + callback: this.sendEditLink, + callbackContext: this, + }, + { + label: L._('Copy link'), + callback: () => { + L.Util.copyToClipboard(data.permissions.anonymous_edit_url) + this.ui.alert({ + content: L._('Secret edit link copied to clipboard!'), + level: 'info', + }) + }, + callbackContext: this, + }, + ] + } + } else if (!this.permissions.isDirty) { + // Do not override local changes to permissions, + // but update in case some other editors changed them in the meantime. + this.permissions.setOptions(data.permissions) + this.permissions.commit() + } + // Update URL in case the name has changed. + if (history && history.pushState) + history.pushState({}, this.options.name, data.url) + else window.location = data.url + alert.content = data.info || alert.content + this.once('saved', () => this.ui.alert(alert)) + this.ui.closePanel() + this.permissions.save() }, save: function () { @@ -1820,23 +1819,6 @@ L.U.Map.include({ this.loader.onAdd(this) }, - post: function (url, options) { - options = options || {} - options.listener = this - this.xhr.post(url, options) - }, - - get: function (url, options) { - options = options || {} - options.listener = this - this.xhr.get(url, options) - }, - - ajax: function (options) { - options.listener = this - this.xhr._ajax(options) - }, - initContextMenu: function () { this.contextmenu = new L.U.ContextMenu(this) this.contextmenu.enable() diff --git a/umap/static/umap/js/umap.layer.js b/umap/static/umap/js/umap.layer.js index 7fafaea1..61218122 100644 --- a/umap/static/umap/js/umap.layer.js +++ b/umap/static/umap/js/umap.layer.js @@ -670,30 +670,26 @@ L.U.DataLayer = L.Evented.extend({ return this }, - fetchData: function () { + fetchData: async function () { if (!this.umap_id) return if (this._loading) return this._loading = true - this.map.get(this._dataUrl(), { - callback: function (geojson, response) { - this._last_modified = response.getResponseHeader('Last-Modified') - // FIXME: for now this property is set dynamically from backend - // And thus it's not in the geojson file in the server - // So do not let all options to be reset - // Fix is a proper migration so all datalayers settings are - // in DB, and we remove it from geojson flat files. - if (geojson._umap_options) { - geojson._umap_options.editMode = this.options.editMode - } - // In case of maps pre 1.0 still around - if (geojson._storage) geojson._storage.editMode = this.options.editMode - this.fromUmapGeoJSON(geojson) - this.backupOptions() - this.fire('loaded') - this._loading = false - }, - context: this, - }) + const [geojson, response] = await this.map.server.get(this._dataUrl()) + this._last_modified = response.headers['Last-Modified'] + // FIXME: for now this property is set dynamically from backend + // And thus it's not in the geojson file in the server + // So do not let all options to be reset + // Fix is a proper migration so all datalayers settings are + // in DB, and we remove it from geojson flat files. + if (geojson._umap_options) { + geojson._umap_options.editMode = this.options.editMode + } + // In case of maps pre 1.0 still around + if (geojson._storage) geojson._storage.editMode = this.options.editMode + this.fromUmapGeoJSON(geojson) + this.backupOptions() + this.fire('loaded') + this._loading = false }, fromGeoJSON: function (geojson) { @@ -1062,13 +1058,10 @@ L.U.DataLayer = L.Evented.extend({ reader.onload = (e) => this.importRaw(e.target.result, type) }, - importFromUrl: function (url, type) { - url = this.map.localizeUrl(url) - this.map.xhr._ajax({ - verb: 'GET', - uri: url, - callback: (data) => this.importRaw(data, type), - }) + importFromUrl: async function (uri, type) { + uri = this.map.localizeUrl(uri) + const response = await this.map.request.get(uri) + this.importRaw(await response.text(), type) }, getColor: function () { @@ -1385,8 +1378,8 @@ L.U.DataLayer = L.Evented.extend({ } }, - buildVersionsFieldset: function (container) { - const appendVersion = function (data) { + buildVersionsFieldset: async function (container) { + const appendVersion = (data) => { const date = new Date(parseInt(data.at, 10)) const content = `${date.toLocaleString(L.lang)} (${parseInt(data.size) / 1000}Kb)` const el = L.DomUtil.create('div', 'umap-datalayer-version', versionsContainer) @@ -1402,34 +1395,24 @@ L.U.DataLayer = L.Evented.extend({ } const versionsContainer = L.DomUtil.createFieldset(container, L._('Versions'), { - callback: function () { - this.map.xhr.get(this.getVersionsUrl(), { - callback: function (data) { - for (let i = 0; i < data.versions.length; i++) { - appendVersion.call(this, data.versions[i]) - } - }, - context: this, - }) + callback: async function () { + const [{versions}, response] = await this.map.server.get(this.getVersionsUrl()) + versions.forEach(appendVersion) }, context: this, }) }, - restore: function (version) { + restore: async function (version) { if (!this.map.editEnabled) return if (!confirm(L._('Are you sure you want to restore this version?'))) return - this.map.xhr.get(this.getVersionUrl(version), { - callback: function (geojson) { - if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat. - if (geojson._umap_options) this.setOptions(geojson._umap_options) - this.empty() - if (this.isRemoteLayer()) this.fetchRemoteData() - else this.addData(geojson) - this.isDirty = true - }, - context: this, - }) + const [geojson, response] = await this.map.server.get(this.getVersionUrl(version)) + if (geojson._storage) geojson._umap_options = geojson._storage // Retrocompat. + if (geojson._umap_options) this.setOptions(geojson._umap_options) + this.empty() + if (this.isRemoteLayer()) this.fetchRemoteData() + else this.addData(geojson) + this.isDirty = true }, featuresToGeoJSON: function () { @@ -1548,7 +1531,7 @@ L.U.DataLayer = L.Evented.extend({ return this.isReadOnly() || this.isRemoteLayer() }, - save: function () { + save: async function () { if (this.isDeleted) return this.saveDelete() if (!this.isLoaded()) { return @@ -1566,44 +1549,35 @@ L.U.DataLayer = L.Evented.extend({ map_id: this.map.options.umap_id, pk: this.umap_id, }) - this.map.post(saveUrl, { - data: formData, - callback: function (data, response) { - // Response contains geojson only if save has conflicted and conflicts have - // been resolved. So we need to reload to get extra data (saved from someone else) - if (data.geojson) { - this.clear() - this.fromGeoJSON(data.geojson) - delete data.geojson - } - this._geojson = geojson - this._last_modified = response.getResponseHeader('Last-Modified') - this.setUmapId(data.id) - this.updateOptions(data) - this.backupOptions() - this.connectToMap() - this._loaded = true - this.redraw() // Needed for reordering features - this.isDirty = false - this.permissions.save() - }, - context: this, - headers: this._last_modified - ? { 'If-Unmodified-Since': this._last_modified } - : {}, - }) + const headers = this._last_modified + ? { 'If-Unmodified-Since': this._last_modified } + : {} + const [data, response] = await this.map.server.post(saveUrl, headers, formData) + // Response contains geojson only if save has conflicted and conflicts have + // been resolved. So we need to reload to get extra data (saved from someone else) + if (data.geojson) { + this.clear() + this.fromGeoJSON(data.geojson) + delete data.geojson + } + this._geojson = geojson + this._last_modified = response.headers['Last-Modified'] + this.setUmapId(data.id) + this.updateOptions(data) + this.backupOptions() + this.connectToMap() + this._loaded = true + this.redraw() // Needed for reordering features + this.isDirty = false + this.permissions.save() }, - saveDelete: function () { - const callback = function () { - this.isDirty = false - this.map.continueSaving() + saveDelete: async function () { + if (this.umap_id) { + await this.map.server.post(this.getDeleteUrl()) } - if (!this.umap_id) return callback.call(this) - this.map.xhr.post(this.getDeleteUrl(), { - callback: callback, - context: this, - }) + this.isDirty = false + this.map.continueSaving() }, getMap: function () { diff --git a/umap/static/umap/js/umap.permissions.js b/umap/static/umap/js/umap.permissions.js index 8945e18e..087f0c0c 100644 --- a/umap/static/umap/js/umap.permissions.js +++ b/umap/static/umap/js/umap.permissions.js @@ -131,21 +131,17 @@ L.U.MapPermissions = L.Class.extend({ this.map.ui.openPanel({ data: { html: container }, className: 'dark' }) }, - attach: function () { - this.map.post(this.getAttachUrl(), { - callback: function () { - this.options.owner = this.map.options.user - this.map.ui.alert({ - content: L._('Map has been attached to your account'), - level: 'info', - }) - this.map.ui.closePanel() - }, - context: this, + attach: async function () { + await this.map.server.post(this.getAttachUrl()) + this.options.owner = this.map.options.user + this.map.ui.alert({ + content: L._('Map has been attached to your account'), + level: 'info', }) + this.map.ui.closePanel() }, - save: function () { + save: async function () { if (!this.isDirty) return this.map.continueSaving() const formData = new FormData() if (!this.isAnonymousMap() && this.options.editors) { @@ -159,16 +155,11 @@ L.U.MapPermissions = L.Class.extend({ formData.append('owner', this.options.owner && this.options.owner.id) formData.append('share_status', this.options.share_status) } - this.map.post(this.getUrl(), { - data: formData, - context: this, - callback: function (data) { - this.commit() - this.isDirty = false - this.map.continueSaving() - this.map.fire('postsync') - }, - }) + await this.map.server.post(this.getUrl(), {}, formData) + this.commit() + this.isDirty = false + this.map.continueSaving() + this.map.fire('postsync') }, getUrl: function () { From 49f600adfae080135ec695a89c4f6d83fe57fa80 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Wed, 24 Jan 2024 11:54:51 +0100 Subject: [PATCH 02/19] wip: refactor login flow Instead of dealing with in JavaScript, let's do a more classic HTTP flow. The main flows work, but there is still at least one to deal with: when editing a map without being logged in, the server may ask for login, and in this case we should login THEN reissue the request, so we need to interrupt the first request in some way, otherwise the server will still answer with a 403, which is what happens after this commit. --- umap/decorators.py | 14 ----- umap/static/umap/content.css | 15 +++++ umap/static/umap/js/modules/request.js | 17 +++-- umap/templates/registration/login.html | 86 +++++++++++++++----------- umap/templates/umap/branding.html | 3 + umap/templates/umap/content.html | 18 ------ umap/templates/umap/navigation.html | 4 +- umap/urls.py | 3 +- 8 files changed, 81 insertions(+), 79 deletions(-) create mode 100644 umap/templates/umap/branding.html diff --git a/umap/decorators.py b/umap/decorators.py index 005e4215..3ae667c5 100644 --- a/umap/decorators.py +++ b/umap/decorators.py @@ -60,17 +60,3 @@ def can_view_map(view_func): return view_func(request, *args, **kwargs) return wrapper - - -def jsonize_view(view_func): - @wraps(view_func) - def wrapper(request, *args, **kwargs): - response = view_func(request, *args, **kwargs) - response_kwargs = {} - if hasattr(response, "rendered_content"): - response_kwargs["html"] = response.rendered_content - if response.has_header("location"): - response_kwargs["redirect"] = response["location"] - return simple_json_response(**response_kwargs) - - return wrapper diff --git a/umap/static/umap/content.css b/umap/static/umap/content.css index ccdb250f..55fb1c98 100644 --- a/umap/static/umap/content.css +++ b/umap/static/umap/content.css @@ -37,6 +37,21 @@ input:-moz-placeholder, :-moz-placeholder { /* **************** */ /* Login icons */ /* **************** */ +body.login { + width: 320px; + margin: auto; + text-align: center; +} +body.login header { + display: flex; + justify-content: center; +} +.login-grid { + display: grid; + justify-content: space-between; + grid-gap: 5px; + grid-template-columns: repeat(auto-fill,92px); +} .login-grid li, .login-grid a { display: inline-block; diff --git a/umap/static/umap/js/modules/request.js b/umap/static/umap/js/modules/request.js index df8c4033..a77ab47b 100644 --- a/umap/static/umap/js/modules/request.js +++ b/umap/static/umap/js/modules/request.js @@ -73,12 +73,12 @@ export const ServerRequest = Request.extend({ headers['X-CSRFToken'] = token } const response = await Request.prototype.post.call(this, uri, headers, data) - return this._handle_json_response(response) + return await this._handle_json_response(response) }, get: async function (uri, headers) { const response = await Request.prototype.get.call(this, uri, headers) - return this._handle_json_response(response) + return await this._handle_json_response(response) }, _handle_json_response: async function (response) { @@ -105,10 +105,15 @@ export const ServerRequest = Request.extend({ this.ui.closePanel() } else if (data.error) { this.ui.alert({ content: data.error, level: 'error' }) - } else if (data.html) { - const ui_options = { data } - let listen_options - this.ui.openPanel(ui_options) + } else if (data.login_required) { + // TODO: stop flow and run request again when user + // is logged in + const win = window.open(data.login_required) + window.umap_proceed = () => { + console.log('logged in') + this.fire('login') + win.close() + } } }, diff --git a/umap/templates/registration/login.html b/umap/templates/registration/login.html index c9edc372..d88bffe6 100644 --- a/umap/templates/registration/login.html +++ b/umap/templates/registration/login.html @@ -1,37 +1,51 @@ -{% load i18n %} -{% if ENABLE_ACCOUNT_LOGIN %} -
{% trans "Please log in with your account" %}
-
- {% if form.non_field_errors %} -
    - {% for error in form.non_field_errors %}
  • {{ error }}
  • {% endfor %} +{% extends "base.html" %} +{% load umap_tags i18n %} +{% block extra_head %} + {% umap_css %} +{% endblock extra_head %} +{% block body_class %} + login +{% endblock body_class %} +{% block content %} +
    +
    + {% include "umap/branding.html" %} +
    + {% if ENABLE_ACCOUNT_LOGIN %} +

    {% trans "Please log in with your account" %}

    +
    + {% if form.non_field_errors %} +
      + {% for error in form.non_field_errors %}
    • {{ error }}
    • {% endfor %} +
    + {% endif %} +
    + {% csrf_token %} + {{ form.username.errors }} + + {{ form.password.errors }} + + +
    +
    + {% endif %} + {% if backends.backends|length %} +

    {% trans "Please choose a provider" %}

    +
    + - {% endif %} -
    - {% csrf_token %} - {{ form.username.errors }} - - {{ form.password.errors }} - - -
    -
    -{% endif %} -{% if backends.backends|length %} -
    {% trans "Please choose a provider" %}
    -
    - -
    -{% endif %} +
+ {% endif %} + +{% endblock content %} diff --git a/umap/templates/umap/branding.html b/umap/templates/umap/branding.html new file mode 100644 index 00000000..b6fb9931 --- /dev/null +++ b/umap/templates/umap/branding.html @@ -0,0 +1,3 @@ +

+ {{ SITE_NAME }} +

diff --git a/umap/templates/umap/content.html b/umap/templates/umap/content.html index a9daf056..2eccf4d1 100644 --- a/umap/templates/umap/content.html +++ b/umap/templates/umap/content.html @@ -40,24 +40,6 @@ !(function () { const ui = new L.U.UI(document.querySelector('header')) - const xhr = new L.U.Xhr(ui) - const login = document.querySelector('a.login') - if (login) { - L.DomEvent.on(login, 'click', function (e) { - L.DomEvent.stop(e) - xhr.login({ - login_required: this.getAttribute('href'), - redirect: '/', - }) - }) - } - const logout = document.querySelector('a.logout') - if (logout) { - L.DomEvent.on(logout, 'click', function (e) { - L.DomEvent.stop(e) - xhr.logout(this.getAttribute('href')) - }) - } const getMore = function (e) { L.DomEvent.stop(e) xhr._ajax({ diff --git a/umap/templates/umap/navigation.html b/umap/templates/umap/navigation.html index fcc41fe0..f8d2dba9 100644 --- a/umap/templates/umap/navigation.html +++ b/umap/templates/umap/navigation.html @@ -1,9 +1,7 @@ {% load i18n %}