Skip to content

Commit 84d21dc

Browse files
committed
take new use case into account mviewer#161 (comment)
1 parent b254207 commit 84d21dc

File tree

8 files changed

+182
-51
lines changed

8 files changed

+182
-51
lines changed

index.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -806,7 +806,7 @@ <h4 class="text-primary" i18n="tabs.publication.subfinish">Vous avez terminé la
806806
<div id="cardsPublication" class="my-5">
807807
<!--Publish-->
808808
<div style="max-width: 18rem;" id="onlineCard">
809-
<a role="button" onclick="mv.publish(config?.id)" href="#">
809+
<a role="button" onclick="mv.showNamePublishModal(config?.id)" href="#">
810810
<div class="card border-info p-3 zoomCard h-100" style="max-width: 18rem; background-color: #f5ae54; color: white;">
811811
<div class="card-body mb-5">
812812
<span class="iconCard-finish">

js/mviewerstudio.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,6 @@ var newConfiguration = function (infos) {
259259
id: infos?.id || mv.uuid(),
260260
description: newDate.format("DD-MM-YYYY-HH-mm-ss"),
261261
isFile: !!infos?.id,
262-
publish: infos?.publish == 'true',
263262
relation: infos?.relation
264263
};
265264
//Store des parametres non gérés
@@ -915,6 +914,7 @@ var saveApplicationParameters = () => {
915914
config.isFile = true;
916915
document.querySelector("#toolsbarStudio-delete").classList.remove("d-none");
917916
document.querySelector("#layerOptionBtn").classList.remove("d-none");
917+
mv.manageDraftBadge(config.relation);
918918
} else {
919919
config.isFile = false;
920920
}

lib/mv.js

+66-15
Original file line numberDiff line numberDiff line change
@@ -1129,6 +1129,7 @@ var mv = (function () {
11291129
var publisher = "anonymous";
11301130
var organisation = _userInfo?.groupFullName || "";
11311131
var description = document.querySelector("#createVersionInput")?.value || data.description
1132+
let relation = data?.relation || "";
11321133
const UUID = data.id;
11331134
const keyworkds = document.querySelector("#optKeywords")?.value;
11341135

@@ -1217,13 +1218,7 @@ var mv = (function () {
12171218
});
12181219
},
12191220

1220-
parseApplication(xml) {
1221-
const app_identifier = xml.getElementsByTagName("dc:identifier")[0]?.innerHTML
1222-
const app_keywords = xml.getElementsByTagName("dc:keywords")[0]?.innerHTML;
1223-
const dateXml = xml.getElementsByTagName("dc:date")[0]?.innerHTML;
1224-
const relation = xml.getElementsByTagName("dc:relation")[0]?.innerHTML;
1225-
const isPublish = xml.getElementsByTagName("config")[0].getAttribute("publish") == "true";
1226-
1221+
manageDraftBadge(isPublish) {
12271222
if (isPublish) {
12281223
document.querySelector("#toolsbarStudio-unpublish").classList.remove("d-none");
12291224
document.querySelector(".badge-publish").classList.remove("d-none");
@@ -1233,11 +1228,20 @@ var mv = (function () {
12331228
document.querySelector(".badge-publish").classList.add("d-none");
12341229
document.querySelector(".badge-draft").classList.remove("d-none");
12351230
}
1231+
},
1232+
1233+
parseApplication(xml) {
1234+
const app_identifier = xml.getElementsByTagName("dc:identifier")[0]?.innerHTML
1235+
const app_keywords = xml.getElementsByTagName("dc:keywords")[0]?.innerHTML;
1236+
const dateXml = xml.getElementsByTagName("dc:date")[0]?.innerHTML;
1237+
const relation = xml.getElementsByTagName("dc:relation")[0]?.innerHTML;
1238+
1239+
mv.manageDraftBadge(relation)
12361240
if (_conf.is_php && onlineCard) {
12371241
onlineCard.classList.add("d-none")
12381242
}
12391243

1240-
newConfiguration({id: app_identifier, isFile: true, date: dateXml, publish: isPublish, relation: relation});
1244+
newConfiguration({id: app_identifier, isFile: true, date: dateXml, relation: relation});
12411245
var proxy = $(xml).find("proxy");
12421246
var olscompletion = $(xml).find("olscompletion");
12431247
if (proxy) {
@@ -1511,8 +1515,8 @@ var mv = (function () {
15111515
</li>`
15121516
}];
15131517

1514-
let badgeLabel = app.publish == "true" ? mviewer.tr("publish") : mviewer.tr("draft");
1515-
let badgeColor = app.publish == "true" ? "badge-publish" : "badge-draft";
1518+
let badgeLabel = app.relation ? mviewer.tr("publish") : mviewer.tr("draft");
1519+
let badgeColor = app.relation ? "badge-publish" : "badge-draft";
15161520
let badge = _conf.is_php ? "" : `<span class="badge ${ badgeColor }">${ badgeLabel }</span>`;
15171521
const items = `
15181522
<div class="list-group-item">
@@ -1961,6 +1965,49 @@ var mv = (function () {
19611965
'data-url':row.xml
19621966
}
19631967
},
1968+
nameNormalizer: (str="") => {
1969+
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^\w ]/g, '_').replace(/\s/g, '_').toLowerCase()
1970+
},
1971+
onChangeName: ({value}, defaultValue) => {
1972+
document.querySelector("#sendPublishApp").disabled = conflict && v === defaultName;
1973+
},
1974+
showNamePublishModal: (id, name = "", conflict = false) => {
1975+
if (config.relation) {
1976+
return mv.publish(config.id, config.relation)
1977+
}
1978+
const defaultName = name ? mv.nameNormalizer(name) : mv.nameNormalizer(document.querySelector("#opt-title").value);
1979+
const publishAppModal = new bootstrap.Modal('#genericModal');
1980+
const question = conflict ? "Ce nom existe déjà ! <br> Vous pouvez changer le nom ou annuler." : "Quel nom souhaitez vous utiliser pour la publication ?"
1981+
genericModalContent.innerHTML = "";
1982+
genericModalContent.innerHTML = `
1983+
<div class="modal-header">
1984+
<h5 class="modal-title" i18n="modal.publish.title">Personnalisation</h5>
1985+
<button type="button" onclick="" class="close" data-bs-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
1986+
</div>
1987+
<div class="modal-body">
1988+
<p>
1989+
<strong>${question}</strong>
1990+
</p>
1991+
<!-- input-->
1992+
<div class="form-group">
1993+
<label for="relationPublishName">Nouveau nom :</label>
1994+
<input onchange="${conflict} ? : mv.onChangeName(this, ${defaultName}) : null " maxlength="20" minlength="3" value="${defaultName}" type="text" class="form-control" id="relationPublishName">
1995+
</div>
1996+
<!-- buttons-->
1997+
<p><strong>Que souhaitez-vous faire ?</strong></p>
1998+
<a id="sendPublishApp" class="cardsClose save-close zoomCard" data-bs-dismiss="modal" onclick="mv.publish('${id}', mv.nameNormalizer(document.getElementById('relationPublishName')?.value))">
1999+
<i class="ri-tools-fill"></i>
2000+
<span i18n="tabs.publication.publish_title">${mviewer.tr(conflict ? "tabs.publication.publish_retry" : "tabs.publication.publish_title")}</span>
2001+
</a>
2002+
<a class="cardsClose notsave-close zoomCard" onclick="" data-bs-dismiss="modal">
2003+
<i class="ri-home-2-line"></i>
2004+
<span i18n="cancel">Annuler</span>
2005+
</a>
2006+
<a class="returnConf-close" data-bs-target="#genericModal" data-bs-toggle="modal" aria-label="Close"><i class="ri-arrow-left-line"></i> <span i18n="modal.exit.previous">Retour</span></a>
2007+
</div>
2008+
`;
2009+
publishAppModal.show();
2010+
},
19642011
showPublishModal: (shareLink = "", iframeLink = "", draftLink = "") => {
19652012
const publishModal = new bootstrap.Modal('#genericModal');
19662013
genericModalContent.innerHTML = "";
@@ -1999,16 +2046,16 @@ var mv = (function () {
19992046
`;
20002047
publishModal.show();
20012048
},
2002-
publish: (id) => {
2049+
publish: (id, name = "") => {
20032050
if (!id) {
20042051
return alertCustom("L'ID n'est pas renseigné. Veuillez contacter un administrateur.", "danger");
20052052
}
20062053
if (!config.isFile) {
20072054
return alertCustom("Enregistrez une premère fois avant de publier !", "danger");
20082055
}
2009-
fetch(`${ _conf.api }/${id}/publish`)
2056+
fetch(`${ _conf.api }/${id}/publish/${name}`,)
20102057
.then(r => {
2011-
return r.ok ? r.json() : Promise.reject(r)
2058+
return r.ok ? r.json() : Promise.reject(r);
20122059
})
20132060
.then(data => {
20142061
if (!_conf?.mviewer_publish) {
@@ -2026,15 +2073,19 @@ var mv = (function () {
20262073
alertCustom("L'application a bien été publiée !", "success");
20272074
})
20282075
.catch(err => {
2029-
alertCustom("Une erreur s'est produite. Veuillez contacter un administrateur.", "danger");
2076+
if (err.status == 409) {
2077+
mv.showNamePublishModal(id, name, true);
2078+
} else {
2079+
alertCustom("Une erreur s'est produite. Veuillez contacter un administrateur.", "danger");
2080+
}
20302081
})
20312082
},
20322083
refreshOnPublish: (file) => {
20332084
const url = _conf.mviewer_instance + file;
20342085
loadApplicationParametersFromRemoteFile(url);
20352086
},
20362087
unpublish: (id) => {
2037-
fetch(`${ _conf.api }/${ id }/publish`, { method: "DELETE" })
2088+
fetch(`${ _conf.api }/${ id }/publish/${config.relation}`, { method: "DELETE" })
20382089
.then(r => {
20392090
return r.ok ? r.json() : Promise.reject(r)
20402091
})

mviewerstudio.i18n.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@
147147
"tabs.publication.preview_title" : "Prévisualiser votre application",
148148
"tabs.publication.preview_text" : "Tester et valider votre configuration en live",
149149
"tabs.publication.publish_title" : "Publier votre application",
150+
"tabs.publication.publish_retry" : "Publier avec ce nouveau nom",
151+
"tabs.publication.publish_replace": "Remplacer",
150152
"tabs.publication.publish_text" : "Mettre en ligne votre application et obtenez un lien de partage",
151153
"tabs.data.title": "Thématiques & données",
152154
"tabs.data.themespanel.title": "Panneau des thématiques",
@@ -530,6 +532,8 @@
530532
"version.comment.ph": "ex: Test release without data",
531533
"studio.toolsbar.unpublish": "Unpublish",
532534
"tabs.publication.publish_title" : "Publish application",
533-
"tabs.publication.publish_text" : "Put your application online."
535+
"tabs.publication.publish_text" : "Put your application online.",
536+
"tabs.publication.publish_retry": "Retry with this new name",
537+
"tabs.publication.publish_replace": "Replace"
534538
}
535539
}

srv/python/mviewerstudio_backend/models/config.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class ConfigModel:
2121
publisher: str
2222
subject: str
2323
date: str
24-
publish: bool
24+
relation: str
2525

2626
def as_dict(self):
2727
return {
@@ -35,5 +35,5 @@ def as_dict(self):
3535
"url": self.url,
3636
"subject": self.subject,
3737
"date": self.date,
38-
"publish": self.publish
38+
"relation": self.relation
3939
}

srv/python/mviewerstudio_backend/route.py

+41-17
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from flask import Blueprint, jsonify, Response, request, current_app, redirect
22
from .utils.login_utils import current_user
3-
from .utils.config_utils import Config
3+
from .utils.config_utils import Config, edit_xml_string, read_xml_file_content
44
from .utils.commons import clean_preview, init_preview
55
import hashlib, uuid
66
from os import path, mkdir, remove
@@ -11,7 +11,7 @@
1111
from .utils.git_utils import Git_manager
1212
from .utils.register_utils import from_xml_path
1313

14-
from werkzeug.exceptions import BadRequest, MethodNotAllowed
14+
from werkzeug.exceptions import BadRequest, MethodNotAllowed, Conflict
1515

1616
import logging
1717

@@ -126,44 +126,67 @@ def list_stored_mviewer_config() -> Response:
126126
config["url"] = current_app.config["CONF_PATH_FROM_MVIEWER"] + config["url"]
127127
return jsonify(configs)
128128

129-
@basic_store.route("/api/app/<id>/publish", methods=["GET", "DELETE"])
130-
def publish_mviewer_config(id) -> Response:
129+
@basic_store.route("/api/app/<id>/publish/<name>", methods=["GET", "DELETE"])
130+
def publish_mviewer_config(id, name) -> Response:
131131
"""
132132
Will put online a config.
133133
This route will copy / past XML to publication directory or delete to unpublish.
134134
:param id: configuration UUID
135135
"""
136136
logger.debug("PUBLISH : %s " % id)
137137

138+
xml_publish_name = name
139+
140+
# control publish directory exists
138141
publish_dir = current_app.config["MVIEWERSTUDIO_PUBLISH_PATH"]
139142
if not publish_dir or not path.exists(publish_dir):
140143
return BadRequest("Publish directory does not exists !")
144+
# create or get org parent directory from publication path
145+
org_publish_dir = path.join(current_app.publish_path, current_user.organisation)
146+
if not path.exists(org_publish_dir):
147+
mkdir(org_publish_dir)
148+
149+
# control file to create or replace
150+
past_file = path.join(org_publish_dir, "%s.xml" % xml_publish_name)
151+
if path.exists(past_file) and request.method == "GET":
152+
# read file to replace
153+
content = read_xml_file_content(past_file)
154+
org = content.find(".//metadata/{*}RDF/{*}Description//{*}publisher").text
155+
creator = content.find(".//metadata/{*}RDF/{*}Description//{*}creator").text
156+
identifier = content.find(".//metadata/{*}RDF/{*}Description//{*}identifier").text
157+
lastRelation = content.find(".//metadata/{*}RDF/{*}Description//{*}relation").text
158+
# detect conflict
159+
if lastRelation != xml_publish_name or org != current_user.organisation or creator != current_user.username or identifier != id:
160+
return Conflict("Already exists !")
161+
# replace safely or return bad request
162+
remove(past_file)
141163

164+
# control that workspace to copy exists
142165
workspace = path.join(current_app.config["EXPORT_CONF_FOLDER"], current_user.organisation, id)
143-
144166
if not path.exists(workspace):
145167
return BadRequest("Application does not exists !")
146-
147-
config = current_app.register.read_json(id)
148168

169+
# read config if exists
170+
config = current_app.register.read_json(id)
149171
if not config:
150172
raise BadRequest("This config doesn't exists !")
151173

152174
copy_file = current_app.config["EXPORT_CONF_FOLDER"] + config[0]["url"]
153175
config = from_xml_path(current_app, copy_file)
154176

155-
past_file = path.join(current_app.publish_path, "%s.xml" % id)
156-
157177
# add publish info in XML
158178
if request.method == "GET":
159-
config.xml.set("publish", "true")
179+
edit_xml_string(config.meta, "relation", xml_publish_name)
160180
message = "publish"
161181

162182
# add unpublish info in XML
163183
if request.method == "DELETE":
164-
config.xml.set("publish", "false")
184+
edit_xml_string(config.meta, "relation", "")
185+
remove(past_file)
165186
message = "Unpublish"
187+
past_file = None
166188

189+
# will update XML with correct relation value to map publish and draft files
167190
config.write()
168191

169192
# commit to track this action
@@ -172,15 +195,10 @@ def publish_mviewer_config(id) -> Response:
172195
# update JSON
173196
config.register.update_from_id(id)
174197

175-
if path.exists(past_file):
176-
remove(past_file)
177-
178198
# move to publish directory
179199
if request.method == "GET":
180200
copyfile(copy_file, past_file)
181201

182-
if request.method == "DELETE":
183-
past_file=None
184202
draft_file = current_app.config["CONF_PATH_FROM_MVIEWER"] + config.as_dict()["url"]
185203
return jsonify({"online_file": past_file, "draft_file": draft_file})
186204

@@ -212,7 +230,13 @@ def delete_config_workspace(id = None) -> Response:
212230
if current_user.username == "anonymous" and config[0]["publisher"] != current_app.config["DEFAULT_ORG"]:
213231
logger.debug("DELETE : NOT ALLOWED FOR THIS ANONYMOUS USER - ORG IS NOT DEFAULT")
214232
return MethodNotAllowed("Not allowed !")
215-
233+
# delete publish
234+
if "relation" in config[0] and config[0]["relation"]:
235+
org_publish_dir = path.join(current_app.publish_path, current_user.organisation)
236+
publish_file = path.join(org_publish_dir, "%s.xml" % config[0]["relation"])
237+
if path.exists(publish_file):
238+
remove(publish_file)
239+
216240
# delete in json
217241
current_app.register.delete(id)
218242
# delete dir

0 commit comments

Comments
 (0)