chore: move permissions to modules

This commit is contained in:
Yohan Boniface 2024-07-29 11:55:33 +02:00
parent aae75d86e9
commit e2d8201321
6 changed files with 291 additions and 282 deletions

View file

@ -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 // Uses U.FormBuilder not available as ESM
// FIXME: this module should not depend on Leaflet // FIXME: this module should not depend on Leaflet
@ -18,6 +18,7 @@ import {
uMapAlertConflict as AlertConflict, uMapAlertConflict as AlertConflict,
} from '../../components/alerts/alert.js' } from '../../components/alerts/alert.js'
import { translate } from '../i18n.js' import { translate } from '../i18n.js'
import { DataLayerPermissions } from '../permissions.js'
export const LAYER_TYPES = [DefaultLayer, Cluster, Heat, Choropleth, Categorized] export const LAYER_TYPES = [DefaultLayer, Cluster, Heat, Choropleth, Categorized]
@ -67,7 +68,7 @@ export class DataLayer {
} }
this.backupOptions() this.backupOptions()
this.connectToMap() this.connectToMap()
this.permissions = new U.DataLayerPermissions(this) this.permissions = new DataLayerPermissions(this)
if (!this.umap_id) { if (!this.umap_id) {
if (this.showAtLoad()) this.show() if (this.showAtLoad()) this.show()
this.isDirty = true this.isDirty = true

View file

@ -27,6 +27,7 @@ import Tooltip from './ui/tooltip.js'
import URLs from './urls.js' import URLs from './urls.js'
import * as Utils from './utils.js' import * as Utils from './utils.js'
import { DataLayer, LAYER_TYPES } from './data/layer.js' import { DataLayer, LAYER_TYPES } from './data/layer.js'
import { DataLayerPermissions, MapPermissions } from './permissions.js'
// Import modules and export them to the global scope. // Import modules and export them to the global scope.
// For the not yet module-compatible JS out there. // For the not yet module-compatible JS out there.
@ -41,6 +42,7 @@ window.U = {
Browser, Browser,
Caption, Caption,
DataLayer, DataLayer,
DataLayerPermissions,
Dialog, Dialog,
EditPanel, EditPanel,
Facets, Facets,
@ -50,6 +52,7 @@ window.U = {
HTTPError, HTTPError,
Importer, Importer,
LAYER_TYPES, LAYER_TYPES,
MapPermissions,
NOKError, NOKError,
Orderable, Orderable,
Panel, Panel,

View file

@ -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:')}<br>${
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
)
}
}

View file

@ -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)
},
})

View file

@ -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:')}<br>${
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
]
},
})

View file

@ -47,8 +47,6 @@
<script src="{% static 'umap/js/umap.forms.js' %}" defer></script> <script src="{% static 'umap/js/umap.forms.js' %}" defer></script>
<script src="{% static 'umap/js/umap.icon.js' %}" defer></script> <script src="{% static 'umap/js/umap.icon.js' %}" defer></script>
<script src="{% static 'umap/js/umap.features.js' %}" defer></script> <script src="{% static 'umap/js/umap.features.js' %}" defer></script>
<script src="{% static 'umap/js/umap.permissions.js' %}" defer></script>
<script src="{% static 'umap/js/umap.datalayer.permissions.js' %}" defer></script>
<script src="{% static 'umap/js/umap.controls.js' %}" defer></script> <script src="{% static 'umap/js/umap.controls.js' %}" defer></script>
<script src="{% static 'umap/js/umap.js' %}" defer></script> <script src="{% static 'umap/js/umap.js' %}" defer></script>
<script src="{% static 'umap/js/components/fragment.js' %}" defer></script> <script src="{% static 'umap/js/components/fragment.js' %}" defer></script>