From e2d820132182c998bddc559c83ff6e4b5cc58c81 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Mon, 29 Jul 2024 11:55:33 +0200 Subject: [PATCH] chore: move permissions to modules --- umap/static/umap/js/modules/data/layer.js | 5 +- umap/static/umap/js/modules/global.js | 3 + umap/static/umap/js/modules/permissions.js | 285 ++++++++++++++++++ .../umap/js/umap.datalayer.permissions.js | 70 ----- umap/static/umap/js/umap.permissions.js | 208 ------------- umap/templates/umap/js.html | 2 - 6 files changed, 291 insertions(+), 282 deletions(-) create mode 100644 umap/static/umap/js/modules/permissions.js delete mode 100644 umap/static/umap/js/umap.datalayer.permissions.js delete mode 100644 umap/static/umap/js/umap.permissions.js diff --git a/umap/static/umap/js/modules/data/layer.js b/umap/static/umap/js/modules/data/layer.js index 11cf3f40..f0b80a20 100644 --- a/umap/static/umap/js/modules/data/layer.js +++ b/umap/static/umap/js/modules/data/layer.js @@ -1,4 +1,4 @@ -// Uses U.DataLayerPermissions, U.Marker, U.Polygon, U.Polyline, U.TableEditor not yet ES modules +// Uses U.Marker, U.Polygon, U.Polyline, U.TableEditor not yet ES modules // Uses U.FormBuilder not available as ESM // FIXME: this module should not depend on Leaflet @@ -18,6 +18,7 @@ import { uMapAlertConflict as AlertConflict, } from '../../components/alerts/alert.js' import { translate } from '../i18n.js' +import { DataLayerPermissions } from '../permissions.js' export const LAYER_TYPES = [DefaultLayer, Cluster, Heat, Choropleth, Categorized] @@ -67,7 +68,7 @@ export class DataLayer { } this.backupOptions() this.connectToMap() - this.permissions = new U.DataLayerPermissions(this) + this.permissions = new DataLayerPermissions(this) if (!this.umap_id) { if (this.showAtLoad()) this.show() this.isDirty = true diff --git a/umap/static/umap/js/modules/global.js b/umap/static/umap/js/modules/global.js index d96e3ff1..f9950548 100644 --- a/umap/static/umap/js/modules/global.js +++ b/umap/static/umap/js/modules/global.js @@ -27,6 +27,7 @@ import Tooltip from './ui/tooltip.js' import URLs from './urls.js' import * as Utils from './utils.js' import { DataLayer, LAYER_TYPES } from './data/layer.js' +import { DataLayerPermissions, MapPermissions } from './permissions.js' // Import modules and export them to the global scope. // For the not yet module-compatible JS out there. @@ -41,6 +42,7 @@ window.U = { Browser, Caption, DataLayer, + DataLayerPermissions, Dialog, EditPanel, Facets, @@ -50,6 +52,7 @@ window.U = { HTTPError, Importer, LAYER_TYPES, + MapPermissions, NOKError, Orderable, Panel, diff --git a/umap/static/umap/js/modules/permissions.js b/umap/static/umap/js/modules/permissions.js new file mode 100644 index 00000000..a3bcec6d --- /dev/null +++ b/umap/static/umap/js/modules/permissions.js @@ -0,0 +1,285 @@ +import { DomUtil } from '../../vendors/leaflet/leaflet-src.esm.js' +import { translate } from './i18n.js' +import { uMapAlert as Alert } from '../components/alerts/alert.js' +import * as Utils from './utils.js' + +// Dedicated object so we can deal with a separate dirty status, and thus +// call the endpoint only when needed, saving one call at each save. +export class MapPermissions { + constructor(map) { + this.setOptions(map.options.permissions) + this.map = map + this._isDirty = false + } + + set isDirty(status) { + this._isDirty = status + if (status) this.map.isDirty = status + } + + get isDirty() { + return this._isDirty + } + + setOptions(options) { + this.options = Object.assign( + { + owner: null, + editors: [], + share_status: null, + edit_status: null, + }, + options + ) + } + + isOwner() { + return this.map.options.user?.id === this.map.options.permissions.owner?.id + } + + isAnonymousMap() { + return !this.map.options.permissions.owner + } + + getMap() { + return this.map + } + + edit() { + if (this.map.options.editMode !== 'advanced') return + if (!this.map.options.umap_id) { + return Alert.info(translate('Please save the map first')) + } + const container = DomUtil.create('div', 'permissions-panel') + const fields = [] + DomUtil.createTitle(container, translate('Update permissions'), 'icon-key') + if (this.isAnonymousMap()) { + if (this.options.anonymous_edit_url) { + const helpText = `${translate('Secret edit link:')}
${ + this.options.anonymous_edit_url + }` + DomUtil.element({ + tagName: 'p', + className: 'help-text', + innerHTML: helpText, + parent: container, + }) + fields.push([ + 'options.edit_status', + { + handler: 'IntSelect', + label: translate('Who can edit'), + selectOptions: this.map.options.edit_statuses, + helpText: helpText, + }, + ]) + } + } else { + if (this.isOwner()) { + fields.push([ + 'options.edit_status', + { + handler: 'IntSelect', + label: translate('Who can edit'), + selectOptions: this.map.options.edit_statuses, + }, + ]) + fields.push([ + 'options.share_status', + { + handler: 'IntSelect', + label: translate('Who can view'), + selectOptions: this.map.options.share_statuses, + }, + ]) + fields.push([ + 'options.owner', + { handler: 'ManageOwner', label: translate("Map's owner") }, + ]) + } + fields.push([ + 'options.editors', + { handler: 'ManageEditors', label: translate("Map's editors") }, + ]) + } + + const builder = new U.FormBuilder(this, fields) + const form = builder.build() + container.appendChild(form) + if (this.isAnonymousMap() && this.map.options.user) { + // We have a user, and this user has come through here, so they can edit the map, so let's allow to own the map. + // Note: real check is made on the back office anyway. + const advancedActions = DomUtil.createFieldset( + container, + translate('Advanced actions') + ) + const advancedButtons = DomUtil.create('div', 'button-bar', advancedActions) + DomUtil.createButton( + 'button', + advancedButtons, + translate('Attach the map to my account'), + this.attach, + this + ) + } + DomUtil.add('h4', '', container, translate('Datalayers')) + this.map.eachDataLayer((datalayer) => { + datalayer.permissions.edit(container) + }) + this.map.editPanel.open({ content: container, className: 'dark' }) + } + + async attach() { + const [data, response, error] = await this.map.server.post(this.getAttachUrl()) + if (!error) { + this.options.owner = this.map.options.user + Alert.success(translate('Map has been attached to your account')) + this.map.editPanel.close() + } + } + + async save() { + if (!this.isDirty) return this.map.continueSaving() + const formData = new FormData() + if (!this.isAnonymousMap() && this.options.editors) { + const editors = this.options.editors.map((u) => u.id) + for (let i = 0; i < this.options.editors.length; i++) + formData.append('editors', this.options.editors[i].id) + } + if (this.isOwner() || this.isAnonymousMap()) + formData.append('edit_status', this.options.edit_status) + if (this.isOwner()) { + formData.append('owner', this.options.owner?.id) + formData.append('share_status', this.options.share_status) + } + const [data, response, error] = await this.map.server.post( + this.getUrl(), + {}, + formData + ) + if (!error) { + this.commit() + this.isDirty = false + this.map.continueSaving() + this.map.fire('postsync') + } + } + + getUrl() { + return Utils.template(this.map.options.urls.map_update_permissions, { + map_id: this.map.options.umap_id, + }) + } + + getAttachUrl() { + return Utils.template(this.map.options.urls.map_attach_owner, { + map_id: this.map.options.umap_id, + }) + } + + addOwnerLink(element, container) { + if (this.options.owner?.name && this.options.owner.url) { + const ownerContainer = DomUtil.add( + element, + 'umap-map-owner', + container, + ` ${translate('by')} ` + ) + DomUtil.createLink( + '', + ownerContainer, + this.options.owner.name, + this.options.owner.url + ) + } + } + + commit() { + this.map.options.permissions = Object.assign( + this.map.options.permissions, + this.options + ) + } + + getShareStatusDisplay() { + return Object.fromEntries(this.map.options.share_statuses)[ + this.options.share_status + ] + } +} + +export class DataLayerPermissions { + constructor(datalayer) { + this.options = Object.assign( + { + edit_status: null, + }, + datalayer.options.permissions + ) + + this.datalayer = datalayer + this._isDirty = false + } + + set isDirty(status) { + this._isDirty = status + if (status) this.datalayer.isDirty = status + } + + get isDirty() { + return this._isDirty + } + + getMap() { + return this.datalayer.map + } + + edit(container) { + const fields = [ + [ + 'options.edit_status', + { + handler: 'IntSelect', + label: translate('Who can edit "{layer}"', { + layer: this.datalayer.getName(), + }), + selectOptions: this.datalayer.map.options.datalayer_edit_statuses, + }, + ], + ] + const builder = new U.FormBuilder(this, fields, { + className: 'umap-form datalayer-permissions', + }) + const form = builder.build() + container.appendChild(form) + } + + getUrl() { + return Utils.template(this.datalayer.map.options.urls.datalayer_permissions, { + map_id: this.datalayer.map.options.umap_id, + pk: this.datalayer.umap_id, + }) + } + async save() { + if (!this.isDirty) return this.datalayer.map.continueSaving() + const formData = new FormData() + formData.append('edit_status', this.options.edit_status) + const [data, response, error] = await this.datalayer.map.server.post( + this.getUrl(), + {}, + formData + ) + if (!error) { + this.commit() + this.isDirty = false + this.datalayer.map.continueSaving() + } + } + + commit() { + this.datalayer.options.permissions = Object.assign( + this.datalayer.options.permissions, + this.options + ) + } +} diff --git a/umap/static/umap/js/umap.datalayer.permissions.js b/umap/static/umap/js/umap.datalayer.permissions.js deleted file mode 100644 index d5a6a288..00000000 --- a/umap/static/umap/js/umap.datalayer.permissions.js +++ /dev/null @@ -1,70 +0,0 @@ -U.DataLayerPermissions = L.Class.extend({ - options: { - edit_status: null, - }, - - initialize: function (datalayer) { - this.options = L.Util.setOptions(this, datalayer.options.permissions) - this.datalayer = datalayer - let isDirty = false - try { - Object.defineProperty(this, 'isDirty', { - get: () => isDirty, - set: (status) => { - isDirty = status - if (status) this.datalayer.isDirty = status - }, - }) - } catch (e) { - // Certainly IE8, which has a limited version of defineProperty - } - }, - - getMap: function () { - return this.datalayer.map - }, - - edit: function (container) { - const fields = [ - [ - 'options.edit_status', - { - handler: 'IntSelect', - label: L._('Who can edit "{layer}"', { layer: this.datalayer.getName() }), - selectOptions: this.datalayer.map.options.datalayer_edit_statuses, - }, - ], - ] - const builder = new U.FormBuilder(this, fields, { - className: 'umap-form datalayer-permissions', - }) - const form = builder.build() - container.appendChild(form) - }, - - getUrl: function () { - return U.Utils.template(this.datalayer.map.options.urls.datalayer_permissions, { - map_id: this.datalayer.map.options.umap_id, - pk: this.datalayer.umap_id, - }) - }, - save: async function () { - if (!this.isDirty) return this.datalayer.map.continueSaving() - const formData = new FormData() - formData.append('edit_status', this.options.edit_status) - const [data, response, error] = await this.datalayer.map.server.post( - this.getUrl(), - {}, - formData - ) - if (!error) { - this.commit() - this.isDirty = false - this.datalayer.map.continueSaving() - } - }, - - commit: function () { - L.Util.extend(this.datalayer.options.permissions, this.options) - }, -}) diff --git a/umap/static/umap/js/umap.permissions.js b/umap/static/umap/js/umap.permissions.js deleted file mode 100644 index 58cdd2e5..00000000 --- a/umap/static/umap/js/umap.permissions.js +++ /dev/null @@ -1,208 +0,0 @@ -// Dedicated object so we can deal with a separate dirty status, and thus -// call the endpoint only when needed, saving one call at each save. -U.MapPermissions = L.Class.extend({ - options: { - owner: null, - editors: [], - share_status: null, - edit_status: null, - }, - - initialize: function (map) { - this.setOptions(map.options.permissions) - this.map = map - let isDirty = false - try { - Object.defineProperty(this, 'isDirty', { - get: () => isDirty, - set: (status) => { - isDirty = status - if (status) { - this.map.isDirty = status - } - }, - }) - } catch (e) { - // Certainly IE8, which has a limited version of defineProperty - } - }, - - setOptions: function (options) { - this.options = L.Util.setOptions(this, options) - }, - - isOwner: function () { - return ( - this.map.options.user && - this.map.options.permissions.owner && - this.map.options.user.id === this.map.options.permissions.owner.id - ) - }, - - isAnonymousMap: function () { - return !this.map.options.permissions.owner - }, - - getMap: function () { - return this.map - }, - - edit: function () { - if (this.map.options.editMode !== 'advanced') return - if (!this.map.options.umap_id) { - return U.Alert.info(L._('Please save the map first')) - } - const container = L.DomUtil.create('div', 'permissions-panel') - const fields = [] - L.DomUtil.createTitle(container, L._('Update permissions'), 'icon-key') - if (this.isAnonymousMap()) { - if (this.options.anonymous_edit_url) { - const helpText = `${L._('Secret edit link:')}
${ - this.options.anonymous_edit_url - }` - L.DomUtil.element({ - tagName: 'p', - className: 'help-text', - innerHTML: helpText, - parent: container, - }) - fields.push([ - 'options.edit_status', - { - handler: 'IntSelect', - label: L._('Who can edit'), - selectOptions: this.map.options.edit_statuses, - helpText: helpText, - }, - ]) - } - } else { - if (this.isOwner()) { - fields.push([ - 'options.edit_status', - { - handler: 'IntSelect', - label: L._('Who can edit'), - selectOptions: this.map.options.edit_statuses, - }, - ]) - fields.push([ - 'options.share_status', - { - handler: 'IntSelect', - label: L._('Who can view'), - selectOptions: this.map.options.share_statuses, - }, - ]) - fields.push([ - 'options.owner', - { handler: 'ManageOwner', label: L._("Map's owner") }, - ]) - } - fields.push([ - 'options.editors', - { handler: 'ManageEditors', label: L._("Map's editors") }, - ]) - } - - const builder = new U.FormBuilder(this, fields) - const form = builder.build() - container.appendChild(form) - if (this.isAnonymousMap() && this.map.options.user) { - // We have a user, and this user has come through here, so they can edit the map, so let's allow to own the map. - // Note: real check is made on the back office anyway. - const advancedActions = L.DomUtil.createFieldset( - container, - L._('Advanced actions') - ) - const advancedButtons = L.DomUtil.create('div', 'button-bar', advancedActions) - L.DomUtil.createButton( - 'button', - advancedButtons, - L._('Attach the map to my account'), - this.attach, - this - ) - } - L.DomUtil.add('h4', '', container, L._('Datalayers')) - this.map.eachDataLayer((datalayer) => { - datalayer.permissions.edit(container) - }) - this.map.editPanel.open({ content: container, className: 'dark' }) - }, - - attach: async function () { - const [data, response, error] = await this.map.server.post(this.getAttachUrl()) - if (!error) { - this.options.owner = this.map.options.user - U.Alert.success(L._('Map has been attached to your account')) - this.map.editPanel.close() - } - }, - - save: async function () { - if (!this.isDirty) return this.map.continueSaving() - const formData = new FormData() - if (!this.isAnonymousMap() && this.options.editors) { - const editors = this.options.editors.map((u) => u.id) - for (let i = 0; i < this.options.editors.length; i++) - formData.append('editors', this.options.editors[i].id) - } - if (this.isOwner() || this.isAnonymousMap()) - formData.append('edit_status', this.options.edit_status) - if (this.isOwner()) { - formData.append('owner', this.options.owner?.id) - formData.append('share_status', this.options.share_status) - } - const [data, response, error] = await this.map.server.post( - this.getUrl(), - {}, - formData - ) - if (!error) { - this.commit() - this.isDirty = false - this.map.continueSaving() - this.map.fire('postsync') - } - }, - - getUrl: function () { - return U.Utils.template(this.map.options.urls.map_update_permissions, { - map_id: this.map.options.umap_id, - }) - }, - - getAttachUrl: function () { - return U.Utils.template(this.map.options.urls.map_attach_owner, { - map_id: this.map.options.umap_id, - }) - }, - - addOwnerLink: function (element, container) { - if (this.options.owner?.name && this.options.owner.url) { - const ownerContainer = L.DomUtil.add( - element, - 'umap-map-owner', - container, - ` ${L._('by')} ` - ) - L.DomUtil.createLink( - '', - ownerContainer, - this.options.owner.name, - this.options.owner.url - ) - } - }, - - commit: function () { - L.Util.extend(this.map.options.permissions, this.options) - }, - - getShareStatusDisplay: function () { - return Object.fromEntries(this.map.options.share_statuses)[ - this.options.share_status - ] - }, -}) diff --git a/umap/templates/umap/js.html b/umap/templates/umap/js.html index a997056d..187d3314 100644 --- a/umap/templates/umap/js.html +++ b/umap/templates/umap/js.html @@ -47,8 +47,6 @@ - -