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 @@
-
-