Browse Source

completed CRUD operations

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
main
Stephan Richter 4 months ago
parent
commit
c5ec3cc43b
  1. 1
      de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/BaseImporter.java
  2. 23
      de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/Util.java
  3. 2
      de.srsoftware.cal.db/build.gradle.kts
  4. 7
      de.srsoftware.cal.db/src/main/java/de/srsoftware/cal/db/Database.java
  5. 32
      de.srsoftware.cal.db/src/main/java/de/srsoftware/cal/db/MariaDB.java
  6. 245
      de.srsoftware.cal.web/src/main/java/de/srsoftware/cal/ApiHandler.java
  7. 9
      de.srsoftware.cal.web/src/main/resources/edit.html
  8. 19
      de.srsoftware.cal.web/src/main/resources/event.html
  9. 6
      de.srsoftware.cal.web/src/main/resources/index.html
  10. 24
      de.srsoftware.cal.web/src/main/resources/occ.css
  11. 56
      de.srsoftware.cal.web/src/main/resources/script/edit.js
  12. 51
      de.srsoftware.cal.web/src/main/resources/script/event.js
  13. 19
      de.srsoftware.cal.web/src/main/resources/script/index.js

1
de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/BaseImporter.java

@ -70,6 +70,7 @@ public abstract class BaseImporter implements Importer { @@ -70,6 +70,7 @@ public abstract class BaseImporter implements Importer {
return Error.of("not implemented");
}
protected Result<LocalDateTime> extractEnd(Tag eventTag) {
Result<Tag> endTag = extractEndTag(eventTag);
if (endTag.optional().isEmpty()) return transform(endTag);

23
de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/Util.java

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
/* © SRSoftware 2024 */
package de.srsoftware.cal;
import de.srsoftware.cal.api.Coords;
import de.srsoftware.tools.Error;
import de.srsoftware.tools.Payload;
import de.srsoftware.tools.Result;
public class Util {
public static Result<Coords> extractCoords(String coords) {
if (coords == null) return de.srsoftware.tools.Error.of("Argument is null");
if (coords.isBlank()) return de.srsoftware.tools.Error.of("Argument is blank");
var parts = coords.split(",");
if (parts.length != 2) return de.srsoftware.tools.Error.format("Argument has invalid format: %s", coords);
try {
var lat = Double.parseDouble(parts[0].trim());
var lon = Double.parseDouble(parts[1].trim());
return Payload.of(new Coords(lon, lat));
} catch (NumberFormatException nfe) {
return Error.of("Failed to parse coords from %s".formatted(coords), nfe);
}
}
}

2
de.srsoftware.cal.db/build.gradle.kts

@ -4,7 +4,7 @@ dependencies { @@ -4,7 +4,7 @@ dependencies {
implementation(project(":de.srsoftware.cal.api"))
implementation(project(":de.srsoftware.cal.base"))
implementation("de.srsoftware:tools.jdbc:1.1.2")
implementation("de.srsoftware:tools.jdbc:1.1.3")
implementation("de.srsoftware:tools.optionals:1.0.0")
implementation("de.srsoftware:tools.util:1.2.3")
}

7
de.srsoftware.cal.db/src/main/java/de/srsoftware/cal/db/Database.java

@ -2,10 +2,13 @@ @@ -2,10 +2,13 @@
package de.srsoftware.cal.db;
import de.srsoftware.cal.api.Appointment;
import de.srsoftware.tools.Error;
import de.srsoftware.tools.Result;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
/**
@ -19,6 +22,8 @@ public interface Database { @@ -19,6 +22,8 @@ public interface Database {
*/
public Result<Appointment> add(Appointment appointment);
Result<List<String>> findTags(String infix);
/**
* list appointments unfiltered
* @param count the maximum number of appointments to return
@ -50,7 +55,7 @@ public interface Database { @@ -50,7 +55,7 @@ public interface Database {
Result<Appointment> loadEvent(String location, LocalDateTime start);
Result<List<String>> findTags(String infix);
Result<Long> removeAppointment(long id);
Result<Appointment> update(Appointment baseAppointment);
}

32
de.srsoftware.cal.db/src/main/java/de/srsoftware/cal/db/MariaDB.java

@ -10,6 +10,7 @@ import static de.srsoftware.tools.jdbc.Query.*; @@ -10,6 +10,7 @@ import static de.srsoftware.tools.jdbc.Query.*;
import static java.lang.System.Logger.Level.*;
import de.srsoftware.cal.BaseAppointment;
import de.srsoftware.cal.Util;
import de.srsoftware.cal.api.Appointment;
import de.srsoftware.cal.api.Attachment;
import de.srsoftware.cal.api.Link;
@ -251,17 +252,25 @@ public class MariaDB implements Database { @@ -251,17 +252,25 @@ public class MariaDB implements Database {
private Result<BaseAppointment> createAppointmentOf(ResultSet results) throws SQLException {
var id = results.getInt(AID);
var title = results.getString("title");
var description = results.getString("description");
var title = results.getString(TITLE);
var description = results.getString(DESCRIPTION);
if (allEmpty(title, description)) return Error.format("Title and Description of appointment %s are empty", id);
var start = results.getTimestamp("start").toLocalDateTime();
var end = nullable(results.getTimestamp("end")).map(Timestamp::toLocalDateTime).orElse(null);
var location = results.getString("location");
var start = results.getTimestamp(START).toLocalDateTime();
var end = nullable(results.getTimestamp(END)).map(Timestamp::toLocalDateTime).orElse(null);
var location = results.getString(LOCATION);
var appointment = new BaseAppointment(id, title, description, start, end, location);
try {
Util.extractCoords(results.getString(COORDS)).optional().ifPresent(appointment::coords);
} catch (SQLException e) {
LOG.log(WARNING, "Failed to read coordinates from database!");
}
try {
var tags = nullIfEmpty(results.getString("tags"));
if (tags != null) appointment.tags(tags.split(","));
} catch (SQLException e) {
LOG.log(WARNING, "Failed to read tags from database!");
}
return Payload.of(appointment);
}
@ -290,6 +299,19 @@ public class MariaDB implements Database { @@ -290,6 +299,19 @@ public class MariaDB implements Database {
return List.of();
}
@Override
public Result<Long> removeAppointment(long id) {
try {
Query.delete().from(APPOINTMENTS).where(AID,equal(id)).execute(connection);
Query.delete().from(APPOINTMENT_TAGS).where(AID,equal(id)).execute(connection);
Query.delete().from(APPOINTMENT_ATTACHMENTS).where(AID,equal(id)).execute(connection);
Query.delete().from(APPOINTMENT_URLS).where(AID,equal(id)).execute(connection);
return Payload.of(id);
} catch (SQLException e) {
return Error.of("Failed to delete event %s".formatted(id),e);
}
}
@Override
public Result<Appointment> update(Appointment event) {
var end = event.end().map(Timestamp::valueOf).orElse(null);

245
de.srsoftware.cal.web/src/main/java/de/srsoftware/cal/ApiHandler.java

@ -11,10 +11,8 @@ import com.sun.net.httpserver.HttpExchange; @@ -11,10 +11,8 @@ import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.cal.api.Appointment;
import de.srsoftware.cal.api.Link;
import de.srsoftware.cal.db.Database;
import de.srsoftware.tools.*;
import de.srsoftware.tools.Error;
import de.srsoftware.tools.PathHandler;
import de.srsoftware.tools.Payload;
import de.srsoftware.tools.Result;
import java.io.IOException;
import java.sql.SQLException;
import java.time.LocalDate;
@ -22,6 +20,7 @@ import java.time.LocalDateTime; @@ -22,6 +20,7 @@ import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.json.JSONObject;
public class ApiHandler extends PathHandler {
@ -44,12 +43,42 @@ public class ApiHandler extends PathHandler { @@ -44,12 +43,42 @@ public class ApiHandler extends PathHandler {
case "/tags" -> listTags(ex,params);
default -> PathHandler.notFound(ex);
};
}
@Override
public boolean doPost(String path, HttpExchange ex) throws IOException {
public boolean doDelete(String path, HttpExchange ex) throws IOException {
var params = queryParam(ex);
return switch (path) {
case "/event" -> delete(ex,params);
default -> PathHandler.notFound(ex);
};
}
private boolean delete(HttpExchange ex, Map<String,String> params) throws IOException {
var aid = params.get(AID);
if (aid == null) return sendContent(ex,Error.of("Missing appointment id"));
long id = 0;
try {
id = Long.parseLong(aid);
} catch (Exception e){
return sendContent(ex,Error.format("%s is not a valid id!",aid));
}
var json = json(ex);
var title = json.has(TITLE) ? nullIfEmpty(json.getString(TITLE)) :
null;
if (title == null) return sendContent(ex, Error.of("Missing appointment title"));
var res = db.loadEvent(id);
if (res.optional().isEmpty()) return sendContent(ex, res);
var event = res.optional().get();
if (!title.equals(event.title())) return sendContent(ex, Error.of("Title mismatch!"));
var del = db.removeAppointment(id);
if (del.optional().isEmpty()) return sendContent(ex,del);
return sendContent(ex,Map.of("deleted",del.optional().get()));
}
@Override
public boolean doPost(String path, HttpExchange ex) throws IOException {
return switch (path) {
case "/event/edit" -> editEvent(ex);
default -> PathHandler.notFound(ex);
};
@ -58,129 +87,135 @@ public class ApiHandler extends PathHandler { @@ -58,129 +87,135 @@ public class ApiHandler extends PathHandler {
// spotless:off
private boolean editEvent(HttpExchange ex) throws IOException {
var json = json(ex);
var location = json.has(LOCATION) ? json.getString(LOCATION) : null;
var start = json.has(START) ? LocalDateTime.parse(json.getString(START)) : null;
if (allSet(location, start)) {
var existingAppointment = db.loadEvent(location, start).optional();
if (existingAppointment.isPresent()) {
var event = existingAppointment.get();
json.put(AID,event.id());
return update(ex,toEvent(json));
Optional<Appointment> existingAppointment = Optional.empty();
Long aid = json.has(AID) ? json.getLong(AID) : null;
if (aid != null) { // load appointment by aid
existingAppointment = db.loadEvent(aid).optional();
} else { // try to load appointment by location @ time
var location = json.has(LOCATION) ? json.getString(LOCATION) : null;
var start = json.has(START) ? LocalDateTime.parse(json.getString(START)) : null;
if (allSet(location, start)) {
existingAppointment = db.loadEvent(location, start).optional();
}
}
if (existingAppointment.isPresent()) {
var event = existingAppointment.get();
json.put(AID,event.id());
return update(ex,toEvent(json));
}
return createEvent(ex, json);
}
// spotless:on
private Result<BaseAppointment> toEvent(JSONObject json) {
var description = json.has(DESCRIPTION) ? nullIfEmpty(json.getString(DESCRIPTION)) : null;
var title = json.has(TITLE) ? nullIfEmpty(json.getString(TITLE)) : null;
if (title == null) return Error.of("title missing");
var start = json.has(START) ? nullIfEmpty(json.getString(START)) : null;
if (start == null) return Error.of("start missing");
var startDate = nullable(start).map(dt -> LocalDateTime.parse(dt, ISO_DATE_TIME)).orElse(null);
var end = json.has(END) ? nullIfEmpty(json.getString(END)) : null;
var endDate = nullable(end).map(dt -> LocalDateTime.parse(dt, ISO_DATE_TIME)).orElse(null);
var location = json.has(LOCATION) ? json.getString(LOCATION) : null;
if (location == null) return Error.of("location missing");
var aid = json.has(AID) ? json.getLong(AID) : 0;
var event = new BaseAppointment(aid, title, description, startDate, endDate, location);
if (json.has(ATTACHMENTS)) {
json.getJSONArray(ATTACHMENTS).forEach(att -> {
Payload //
.of(att.toString())
.map(BaseImporter::url)
.map(BaseImporter::toAttachment)
.optional()
.ifPresent(event::add);
});
}
if (json.has(LINKS)) {
json.getJSONArray(LINKS).forEach(o -> {
if (o instanceof JSONObject j) toLink(j).optional().ifPresent(event::addLinks);
});
private Result<BaseAppointment> toEvent(JSONObject json) {
var description = json.has(DESCRIPTION) ? nullIfEmpty(json.getString(DESCRIPTION)) : null;
var title = json.has(TITLE) ? nullIfEmpty(json.getString(TITLE)) : null;
if (title == null) return Error.of("title missing");
var start = json.has(START) ? nullIfEmpty(json.getString(START)) : null;
if (start == null) return Error.of("start missing");
var startDate = nullable(start).map(dt -> LocalDateTime.parse(dt, ISO_DATE_TIME)).orElse(null);
var end = json.has(END) ? nullIfEmpty(json.getString(END)) : null;
var endDate = nullable(end).map(dt -> LocalDateTime.parse(dt, ISO_DATE_TIME)).orElse(null);
var location = json.has(LOCATION) ? json.getString(LOCATION) : null;
if (location == null) return Error.of("location missing");
var aid = json.has(AID) ? json.getLong(AID) : 0;
var event = new BaseAppointment(aid, title, description, startDate, endDate, location);
if (json.has(ATTACHMENTS)) {
json.getJSONArray(ATTACHMENTS).forEach(att -> {
Payload //
.of(att.toString())
.map(BaseImporter::url)
.map(BaseImporter::toAttachment)
.optional()
.ifPresent(event::add);
});
}
if (json.has(LINKS)) {
json.getJSONArray(LINKS).forEach(o -> {
if (o instanceof JSONObject j) toLink(j).optional().ifPresent(event::addLinks);
});
}
if (json.has(TAGS)) json.getJSONArray(TAGS).forEach(o -> event.tags(o.toString()));
return Payload.of(event);
}
if (json.has(TAGS)) json.getJSONArray(TAGS).forEach(o -> event.tags(o.toString()));
return Payload.of(event);
}
private boolean createEvent(HttpExchange ex, JSONObject json) throws IOException {
var eventRes = toEvent(json);
if (eventRes.optional().isPresent()) {
return sendContent(ex, db.add(eventRes.optional().get()).map(ApiHandler::toJson));
private boolean createEvent(HttpExchange ex, JSONObject json) throws IOException {
var eventRes = toEvent(json);
if (eventRes.optional().isPresent()) {
return sendContent(ex, db.add(eventRes.optional().get()).map(ApiHandler::toJson));
}
return sendContent(ex, eventRes);
}
return sendContent(ex, eventRes);
}
protected static Result<Link> toLink(JSONObject json) {
try {
var description = json.getString(DESCRIPTION);
return Payload.of(json.getString(URL)).map(BaseImporter::url).map(url -> BaseImporter.link(url, description));
protected static Result<Link> toLink(JSONObject json) {
try {
var description = json.getString(DESCRIPTION);
return Payload.of(json.getString(URL)).map(BaseImporter::url).map(url -> BaseImporter.link(url, description));
} catch (Exception e) {
return Error.of("Failed to create link from %s".formatted(json), e);
} catch (Exception e) {
return Error.of("Failed to create link from %s".formatted(json), e);
}
}
}
private boolean update(HttpExchange ex, Result<BaseAppointment> event) throws IOException {
if (event.optional().isPresent()) return sendContent(ex, db.update(event.optional().get()).map(ApiHandler::toJson));
return sendContent(ex, event);
}
private boolean listTags(HttpExchange ex, Map<String, String> params) throws IOException {
var infix = params.get("infix");
if (infix == null) return sendContent(ex, Error.of("No infix set in method call parameters"));
var res = db.findTags(infix).map(ApiHandler::sortTags);
return sendContent(ex, res);
}
private boolean update(HttpExchange ex, Result<BaseAppointment> event) throws IOException {
if (event.optional().isPresent()) return sendContent(ex, db.update(event.optional().get()).map(ApiHandler::toJson));
return sendContent(ex, event);
}
private static Result<List<String>> sortTags(Result<List<String>> listResult) {
if (listResult.optional().isEmpty()) return listResult;
List<String> list = listResult.optional().get();
while (list.size() > 15) {
int longest = list.stream().map(String::length).reduce(0, Integer::max);
var subset = list.stream().filter(s -> s.length() < longest).toList();
if (subset.size() < 3) return Payload.of(list);
list = subset;
private boolean listTags(HttpExchange ex, Map<String, String> params) throws IOException {
var infix = params.get("infix");
if (infix == null) return sendContent(ex, Error.of("No infix set in method call parameters"));
var res = db.findTags(infix).map(ApiHandler::sortTags);
return sendContent(ex, res);
}
return Payload.of(list);
}
private boolean listEvents(HttpExchange ex, Map<String, String> params) throws IOException {
var start = nullable(params.get("start")).map(ApiHandler::toLocalDateTime).orElse(null);
var end = nullable(params.get("end")).map(ApiHandler::toLocalDateTime).orElse(null);
try {
return PathHandler.sendContent(ex, db.list(start, end).stream().map(Appointment::json).toList());
} catch (SQLException e) {
LOG.log(WARNING, "Failed to fetch events (start = {0}, end = {1}!", start, end, e);
private static Result<List<String>> sortTags(Result<List<String>> listResult) {
if (listResult.optional().isEmpty()) return listResult;
List<String> list = listResult.optional().get();
while (list.size() > 15) {
int longest = list.stream().map(String::length).reduce(0, Integer::max);
var subset = list.stream().filter(s -> s.length() < longest).toList();
if (subset.size() < 3) return Payload.of(list);
list = subset;
}
return Payload.of(list);
}
return PathHandler.notFound(ex);
}
private boolean loadEvent(HttpExchange ex, Map<String, String> params) throws IOException {
var id = params.get("id");
if (id != null) try {
return PathHandler.sendContent(ex, db.loadEvent(Long.parseLong(id)).map(ApiHandler::toJson));
} catch (NumberFormatException | IOException nfe) {
return PathHandler.sendContent(ex, Error.format("%s is not a numeric event id!", id));
private boolean listEvents(HttpExchange ex, Map<String, String> params) throws IOException {
var start = nullable(params.get("start")).map(ApiHandler::toLocalDateTime).orElse(null);
var end = nullable(params.get("end")).map(ApiHandler::toLocalDateTime).orElse(null);
try {
return PathHandler.sendContent(ex, db.list(start, end).stream().map(Appointment::json).toList());
} catch (SQLException e) {
LOG.log(WARNING, "Failed to fetch events (start = {0}, end = {1}!", start, end, e);
}
return PathHandler.sendContent(ex, Error.of("ID missing"));
}
return PathHandler.notFound(ex);
}
private static Result<JSONObject> toJson(Result<Appointment> res) {
var opt = res.optional();
if (opt.isEmpty()) return Result.transform(res);
return Payload.of(opt.get().json());
}
private boolean loadEvent(HttpExchange ex, Map<String, String> params) throws IOException {
var id = params.get("id");
if (id != null) try {
return PathHandler.sendContent(ex, db.loadEvent(Long.parseLong(id)).map(ApiHandler::toJson));
} catch (NumberFormatException | IOException nfe) {
return PathHandler.sendContent(ex, Error.format("%s is not a numeric event id!", id));
}
return PathHandler.sendContent(ex, Error.of("ID missing"));
}
private static Result<JSONObject> toJson(Result<Appointment> res) {
var opt = res.optional();
if (opt.isEmpty()) return Result.transform(res);
return Payload.of(opt.get().json());
}
private static LocalDateTime toLocalDateTime(String dateString) {
try {
return LocalDate.parse(dateString + "-01", DateTimeFormatter.ISO_LOCAL_DATE).atTime(0, 0);
} catch (Exception e) {
return null;
private static LocalDateTime toLocalDateTime(String dateString) {
try {
return LocalDate.parse(dateString + "-01", DateTimeFormatter.ISO_LOCAL_DATE).atTime(0, 0);
} catch (Exception e) {
return null;
}
}
}
}

9
de.srsoftware.cal.web/src/main/resources/edit.html

@ -9,9 +9,13 @@ @@ -9,9 +9,13 @@
<script src="/static/script/leaflet.js"></script>
</head>
<body>
<h1>Create new event</h1>
<button id="save" disabled>save</button>
<h1>Create/Edit event</h1>
<span id="buttons">
<button id="save" disabled>save</button>
<button id="cancel" onclick="location.href = location.href.replace('/static/edit','').replace('copy=','id=')">cancel</button>
</span>
<div class="form">
<input type="hidden" id="aid" />
<fieldset>
<legend>basic data</legend>
<table>
@ -113,5 +117,6 @@ @@ -113,5 +117,6 @@
</div>
<script>document.addEventListener("DOMContentLoaded", loadEvent);</script>
</body>
</html>

19
de.srsoftware.cal.web/src/main/resources/event.html

@ -5,20 +5,37 @@ @@ -5,20 +5,37 @@
<link rel="stylesheet" href="occ.css" />
<script src="/static/script/common.js"></script>
<script src="/static/script/event.js"></script>
<link rel="stylesheet" href="/static/leaflet.css" />
<script src="/static/script/leaflet.js"></script>
</head>
<body class="event">
<nav />
<h1>Loading…</h1>
<h1 id="title">Loading…</h1>
<span id="buttons">
<button title="create a copy of this event" onclick="window.top.location.href = location.href.replace('/static/event','/static/edit').replace('id=','copy=')">⧉ copy</button>
<button title="edit the details of this event" onclick="window.top.location.href = location.href.replace('/static/event','/static/edit')">✍ edit</button>
<button title="delete this event" onclick="confirmDelete()">🗑 delete</button>
</span>
<div id="time">Loading time…</div>
<div id="description">Loading description…</div>
<div id="links">Loading links…</div>
<div id="attachments">Loading attachments…</div>
<div id="tags">Loading tags…</div>
<div id="map" style="width: 100%; height: 500px;"></div>
<script>
var map = L.map('map').setView([51.160556, 10.4425], 9);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);
document.addEventListener("DOMContentLoaded", function(event){
console.log("page loaded…");
loadEventData();
});
</script>
</body>
</html>

6
de.srsoftware.cal.web/src/main/resources/index.html

@ -27,16 +27,14 @@ @@ -27,16 +27,14 @@
</th>
<th>End</th>
<th>Location</th>
<th>Title</th>
<th>Location</th>
<th>Tags <span id="tag_selection"></span></th>
</tr>
</table>
</div>
<script>
document.addEventListener("DOMContentLoaded", function(event){
loadCurrentEvents();
});
document.addEventListener("DOMContentLoaded", loadCurrentEvents);
</script>
<div id="overlay">

24
de.srsoftware.cal.web/src/main/resources/occ.css

@ -48,14 +48,15 @@ body.event { @@ -48,14 +48,15 @@ body.event {
position: fixed;
top: 20px;
right: 20px;
height: 20px;
width: 20px;
height: 25px;
width: 25px;
background: chocolate;
z-index: 1000;
text-align: center;
border: 1px solid yellow;
border-radius: 5px;
cursor: pointer;
font-size: 20px;
}
#tag_selection {
@ -127,15 +128,22 @@ table#eventlist{ @@ -127,15 +128,22 @@ table#eventlist{
display: none;
}
#buttons {
position: fixed;
display: block;
text-align: center;
top: 10px;
width: 100%;
}
#cancel,
#save {
min-width: 100px;
min-height: 50px;
font-size: 18px;
min-width: 80px;
min-height: 30px;
font-size: 16px;
font-weight: bold;
position: fixed;
top: 15px;
left: 50%;
}
.error {
position: absolute;
left: 0;

56
de.srsoftware.cal.web/src/main/resources/script/edit.js

@ -155,8 +155,10 @@ async function handleSave(response){ @@ -155,8 +155,10 @@ async function handleSave(response){
if (response.ok){
var json = await response.json();
var url = window.location.href.replace('/static/edit','')
if (json.id) url+='?highlight='+json.id;
window.location.href = url; // TODO: add highlight on new event
let pos = url.indexOf('?');
if (pos>0) url = url.substring(0,pos);
if (json.id) url+='?id='+json.id;
window.location.href = url;
} else {
var json = await response.json();
if (json.error) showError(json.error);
@ -169,6 +171,7 @@ async function saveEvent(){ @@ -169,6 +171,7 @@ async function saveEvent(){
var location = element('location').value;
var start = element('start').value;
var event = {
aid : element('aid').value,
title : element('title').value,
description : element('description').value,
location : element('location').value,
@ -186,4 +189,53 @@ async function saveEvent(){ @@ -186,4 +189,53 @@ async function saveEvent(){
'Content-Type' : 'appication/json'
}
}).then(handleSave);
}
async function distributeEventData(response){
if (response.ok){
let event = await response.json();
console.log({'event-data':event});
var keys = ['title','location','description'];
if (element('aid').value) {
keys.push('start');
keys.push('end');
}
for (var key of keys){
if (event[key]) element(key).value = event[key];
}
if (event.coords){
element('coords').value = event.coords.lat + ', ' + event.coords.lon;
marker.setLatLng([event.coords.lat, event.coords.lon]);
map.setView([event.coords.lat, event.coords.lon], 17);
}
var linkList = element('link-list');
for (var link of event.links){
var span = document.createElement('span');
span.innerHTML = `<a href="${link.url}">${link.description}</a><a onclick="removeParent(this)">❌</a> `;
linkList.appendChild(span);
}
var imgList = element('attachment-list');
for (var image of event.attachments){
var span = document.createElement('span');
span.innerHTML = `<img src="${image.url}" /><a onclick="removeParent(this)">❌</a> `;
imgList.appendChild(span);
}
if (event.tags){
for (var tag of event.tags) addTag(tag);
}
}
}
function loadEvent(){
let params = new URLSearchParams(window.location.search);
var aid = params.get('id');
if (aid){
element('aid').value = aid;
} else aid = params.get('copy');
if (aid) fetch('/api/event?id='+aid).then(distributeEventData);
}

51
de.srsoftware.cal.web/src/main/resources/script/event.js

@ -17,17 +17,21 @@ function attachmentList(json){ @@ -17,17 +17,21 @@ function attachmentList(json){
}
async function handleEventData(response){
if (response.ok){
var json = await response.json();
document.getElementsByTagName('h1')[0].innerHTML = json.title ? json.title : '';
document.getElementById('time').innerHTML = json.start + (json.end ? '…'+json.end : '');
addDescription(json);
document.getElementById('tags').innerHTML = "Tags: "+json.tags.join(" ");
var links = document.getElementById('links');
links.innerHTML = "";
links.appendChild(linkList(json.links));
attachmentList(json.attachments);
}
if (response.ok){
var event = await response.json();
document.getElementsByTagName('h1')[0].innerHTML = event.title ? event.title : '';
document.getElementById('time').innerHTML = event.start + (event.end ? '…'+event.end : '');
addDescription(event);
document.getElementById('tags').innerHTML = "Tags: "+event.tags.join(" ");
var links = document.getElementById('links');
links.innerHTML = "";
links.appendChild(linkList(event.links));
attachmentList(event.attachments);
if (event.coords){
L.marker([event.coords.lat, event.coords.lon]).addTo(map);
map.setView([event.coords.lat, event.coords.lon], 17);
}
}
}
function linkList(json){
@ -50,3 +54,28 @@ function loadEventData(){ @@ -50,3 +54,28 @@ function loadEventData(){
if (id) fetch('/api/event?id='+id).then(handleEventData);
}
async function handleDeleted(response){
if (response.ok){
console.log(location.href);
var url = location.href.replace('/static/event','/');
console.log(url);
var pos = url.indexOf('?');
if (pos>0) url = url.substring(0,pos);
console.log(url);
window.top.location.href = url;
}
}
function confirmDelete(){
var title = element('title').innerHTML;
const urlParams = new URLSearchParams(window.location.search);
var id = urlParams.get('id');
if (confirm(`Do you really want to delete "${title}"?`)){
fetch('/api/event?aid='+id,{
method: 'DELETE',
body: JSON.stringify({
title: title
})
}).then(handleDeleted);
}
}

19
de.srsoftware.cal.web/src/main/resources/script/index.js

@ -23,8 +23,8 @@ function addRow(json){ @@ -23,8 +23,8 @@ function addRow(json){
addCell(row,json.id,json.id);
addCell(row,json.start,json.id);
addCell(row,json.end,json.id);
addCell(row,json.location,json.id);
addCell(row,json.title,json.id);
addCell(row,json.location,json.id);
row.appendChild(createTags(json.tags));
}
@ -80,15 +80,17 @@ async function handleEvents(response){ @@ -80,15 +80,17 @@ async function handleEvents(response){
updateTagVisibility();
if (highlight){
var row = element(highlight);
row.classList.add('highlight');
row.scrollIntoView({behavior: 'smooth',block: 'center'});
if (row) {
row.classList.add('highlight');
row.scrollIntoView({behavior: 'smooth',block: 'center'});
}
}
}
}
function loadCurrentEvents(){
let params = new URLSearchParams(location.search);
highlight = params.get('highlight');
highlight = params.get('id');
if (start == null){
var now = new Date();
var year = now.getFullYear();
@ -106,10 +108,11 @@ function showOverlay(url){ @@ -106,10 +108,11 @@ function showOverlay(url){
div.appendChild(iframe);
div.style.display = 'block';
div.onclick = e => div.style.display = 'none';
var btn = document.createElement('div');
btn.id = 'close_overlay';
btn.innerHTML = '✖';
div.appendChild(btn);
var closeBtn = document.createElement('div');
closeBtn.id = 'close_overlay';
closeBtn.innerHTML = '✖';
closeBtn.title = 'close overlay';
div.appendChild(closeBtn);
}
function toggleTag(tag){

Loading…
Cancel
Save