Skip to content

Commit 48ef3fd

Browse files
committed
take new use case into account mviewer#161 (comment)
1 parent edaf654 commit 48ef3fd

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
@@ -275,7 +275,6 @@ var newConfiguration = function (infos) {
275275
id: infos?.id || mv.uuid(),
276276
description: newDate.format("DD-MM-YYYY-HH-mm-ss"),
277277
isFile: !!infos?.id,
278-
publish: infos?.publish == 'true',
279278
relation: infos?.relation
280279
};
281280
//Store des parametres non gérés
@@ -901,6 +900,7 @@ var saveApplicationParameters = () => {
901900
config.isFile = true;
902901
document.querySelector("#toolsbarStudio-delete").classList.remove("d-none");
903902
document.querySelector("#layerOptionBtn").classList.remove("d-none");
903+
mv.manageDraftBadge(config.relation);
904904
} else {
905905
config.isFile = false;
906906
}

lib/mv.js

+66-15
Original file line numberDiff line numberDiff line change
@@ -1131,6 +1131,7 @@ var mv = (function () {
11311131
var publisher = "anonymous";
11321132
var organisation = _userInfo?.groupFullName || "";
11331133
var description = document.querySelector("#createVersionInput")?.value || data.description
1134+
let relation = data?.relation || "";
11341135
const UUID = data.id;
11351136
const keyworkds = document.querySelector("#optKeywords")?.value;
11361137

@@ -1219,13 +1220,7 @@ var mv = (function () {
12191220
});
12201221
},
12211222

1222-
parseApplication(xml) {
1223-
const app_identifier = xml.getElementsByTagName("dc:identifier")[0]?.innerHTML
1224-
const app_keywords = xml.getElementsByTagName("dc:keywords")[0]?.innerHTML;
1225-
const dateXml = xml.getElementsByTagName("dc:date")[0]?.innerHTML;
1226-
const relation = xml.getElementsByTagName("dc:relation")[0]?.innerHTML;
1227-
const isPublish = xml.getElementsByTagName("config")[0].getAttribute("publish") == "true";
1228-
1223+
manageDraftBadge(isPublish) {
12291224
if (isPublish) {
12301225
document.querySelector("#toolsbarStudio-unpublish").classList.remove("d-none");
12311226
document.querySelector(".badge-publish").classList.remove("d-none");
@@ -1235,11 +1230,20 @@ var mv = (function () {
12351230
document.querySelector(".badge-publish").classList.add("d-none");
12361231
document.querySelector(".badge-draft").classList.remove("d-none");
12371232
}
1233+
},
1234+
1235+
parseApplication(xml) {
1236+
const app_identifier = xml.getElementsByTagName("dc:identifier")[0]?.innerHTML
1237+
const app_keywords = xml.getElementsByTagName("dc:keywords")[0]?.innerHTML;
1238+
const dateXml = xml.getElementsByTagName("dc:date")[0]?.innerHTML;
1239+
const relation = xml.getElementsByTagName("dc:relation")[0]?.innerHTML;
1240+
1241+
mv.manageDraftBadge(relation)
12381242
if (_conf.is_php && onlineCard) {
12391243
onlineCard.classList.add("d-none")
12401244
}
12411245

1242-
newConfiguration({id: app_identifier, isFile: true, date: dateXml, publish: isPublish, relation: relation});
1246+
newConfiguration({id: app_identifier, isFile: true, date: dateXml, relation: relation});
12431247
var proxy = $(xml).find("proxy");
12441248
var olscompletion = $(xml).find("olscompletion");
12451249
if (olscompletion && olscompletion.attr("type")) {
@@ -1510,8 +1514,8 @@ var mv = (function () {
15101514
</li>`
15111515
}];
15121516

1513-
let badgeLabel = app.publish == "true" ? mviewer.tr("publish") : mviewer.tr("draft");
1514-
let badgeColor = app.publish == "true" ? "badge-publish" : "badge-draft";
1517+
let badgeLabel = app.relation ? mviewer.tr("publish") : mviewer.tr("draft");
1518+
let badgeColor = app.relation ? "badge-publish" : "badge-draft";
15151519
let badge = _conf.is_php ? "" : `<span class="badge ${ badgeColor }">${ badgeLabel }</span>`;
15161520
const items = `
15171521
<div class="list-group-item">
@@ -1905,6 +1909,49 @@ var mv = (function () {
19051909
}
19061910
return mv.getListeApplications(value)
19071911
},
1912+
nameNormalizer: (str="") => {
1913+
return str.normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^\w ]/g, '_').replace(/\s/g, '_').toLowerCase()
1914+
},
1915+
onChangeName: ({value}, defaultValue) => {
1916+
document.querySelector("#sendPublishApp").disabled = conflict && v === defaultName;
1917+
},
1918+
showNamePublishModal: (id, name = "", conflict = false) => {
1919+
if (config.relation) {
1920+
return mv.publish(config.id, config.relation)
1921+
}
1922+
const defaultName = name ? mv.nameNormalizer(name) : mv.nameNormalizer(document.querySelector("#opt-title").value);
1923+
const publishAppModal = new bootstrap.Modal('#genericModal');
1924+
const question = conflict ? "Ce nom existe déjà ! <br> Vous pouvez changer le nom ou annuler." : "Quel nom souhaitez vous utiliser pour la publication ?"
1925+
genericModalContent.innerHTML = "";
1926+
genericModalContent.innerHTML = `
1927+
<div class="modal-header">
1928+
<h5 class="modal-title" i18n="modal.publish.title">Personnalisation</h5>
1929+
<button type="button" onclick="" class="close" data-bs-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
1930+
</div>
1931+
<div class="modal-body">
1932+
<p>
1933+
<strong>${question}</strong>
1934+
</p>
1935+
<!-- input-->
1936+
<div class="form-group">
1937+
<label for="relationPublishName">Nouveau nom :</label>
1938+
<input onchange="${conflict} ? : mv.onChangeName(this, ${defaultName}) : null " maxlength="20" minlength="3" value="${defaultName}" type="text" class="form-control" id="relationPublishName">
1939+
</div>
1940+
<!-- buttons-->
1941+
<p><strong>Que souhaitez-vous faire ?</strong></p>
1942+
<a id="sendPublishApp" class="cardsClose save-close zoomCard" data-bs-dismiss="modal" onclick="mv.publish('${id}', mv.nameNormalizer(document.getElementById('relationPublishName')?.value))">
1943+
<i class="ri-tools-fill"></i>
1944+
<span i18n="tabs.publication.publish_title">${mviewer.tr(conflict ? "tabs.publication.publish_retry" : "tabs.publication.publish_title")}</span>
1945+
</a>
1946+
<a class="cardsClose notsave-close zoomCard" onclick="" data-bs-dismiss="modal">
1947+
<i class="ri-home-2-line"></i>
1948+
<span i18n="cancel">Annuler</span>
1949+
</a>
1950+
<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>
1951+
</div>
1952+
`;
1953+
publishAppModal.show();
1954+
},
19081955
showPublishModal: (shareLink = "", iframeLink = "", draftLink = "") => {
19091956
const publishModal = new bootstrap.Modal('#genericModal');
19101957
genericModalContent.innerHTML = "";
@@ -1943,16 +1990,16 @@ var mv = (function () {
19431990
`;
19441991
publishModal.show();
19451992
},
1946-
publish: (id) => {
1993+
publish: (id, name = "") => {
19471994
if (!id) {
19481995
return alertCustom("L'ID n'est pas renseigné. Veuillez contacter un administrateur.", "danger");
19491996
}
19501997
if (!config.isFile) {
19511998
return alertCustom("Enregistrez une premère fois avant de publier !", "danger");
19521999
}
1953-
fetch(`${ _conf.api }/${id}/publish`)
2000+
fetch(`${ _conf.api }/${id}/publish/${name}`,)
19542001
.then(r => {
1955-
return r.ok ? r.json() : Promise.reject(r)
2002+
return r.ok ? r.json() : Promise.reject(r);
19562003
})
19572004
.then(data => {
19582005
if (!_conf?.mviewer_publish) {
@@ -1970,15 +2017,19 @@ var mv = (function () {
19702017
alertCustom("L'application a bien été publiée !", "success");
19712018
})
19722019
.catch(err => {
1973-
alertCustom("Une erreur s'est produite. Veuillez contacter un administrateur.", "danger");
2020+
if (err.status == 409) {
2021+
mv.showNamePublishModal(id, name, true);
2022+
} else {
2023+
alertCustom("Une erreur s'est produite. Veuillez contacter un administrateur.", "danger");
2024+
}
19742025
})
19752026
},
19762027
refreshOnPublish: (file) => {
19772028
const url = _conf.mviewer_instance + file;
19782029
loadApplicationParametersFromRemoteFile(url);
19792030
},
19802031
unpublish: (id) => {
1981-
fetch(`${ _conf.api }/${ id }/publish`, { method: "DELETE" })
2032+
fetch(`${ _conf.api }/${ id }/publish/${config.relation}`, { method: "DELETE" })
19822033
.then(r => {
19832034
return r.ok ? r.json() : Promise.reject(r)
19842035
})

mviewerstudio.i18n.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@
146146
"tabs.publication.preview_title" : "Prévisualiser votre application",
147147
"tabs.publication.preview_text" : "Tester et valider votre configuration en live",
148148
"tabs.publication.publish_title" : "Publier votre application",
149+
"tabs.publication.publish_retry" : "Publier avec ce nouveau nom",
150+
"tabs.publication.publish_replace": "Remplacer",
149151
"tabs.publication.publish_text" : "Mettre en ligne votre application et obtenez un lien de partage",
150152
"tabs.data.title": "Thématiques & données",
151153
"tabs.data.themespanel.title": "Panneau des thématiques",
@@ -523,6 +525,8 @@
523525
"version.comment.ph": "ex: Test release without data",
524526
"studio.toolsbar.unpublish": "Unpublish",
525527
"tabs.publication.publish_title" : "Publish application",
526-
"tabs.publication.publish_text" : "Put your application online."
528+
"tabs.publication.publish_text" : "Put your application online.",
529+
"tabs.publication.publish_retry": "Retry with this new name",
530+
"tabs.publication.publish_replace": "Replace"
527531
}
528532
}

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)