working on event detail page

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
2024-12-26 19:28:24 +01:00
parent 9f806637c2
commit a90627d976
14 changed files with 178 additions and 59 deletions
+7
View File
@@ -1,4 +1,11 @@
description = "OpenCloudCal : Web"
dependencies {
implementation(project(":de.srsoftware.cal.api"))
implementation(project(":de.srsoftware.cal.db"))
implementation("de.srsoftware:tools.http:1.0.3")
implementation("de.srsoftware:tools.optionals:1.0.0")
implementation("de.srsoftware:tools.util:1.2.2")
implementation("org.json:json:20240303")
}
@@ -0,0 +1,78 @@
/* © SRSoftware 2024 */
package de.srsoftware.cal;
import static de.srsoftware.tools.Optionals.nullable;
import static java.lang.System.Logger;
import static java.lang.System.Logger.Level.WARNING;
import static java.lang.System.getLogger;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.cal.api.Appointment;
import de.srsoftware.cal.db.Database;
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;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import org.json.JSONObject;
public class ApiHandler extends PathHandler {
private static final Logger LOG = getLogger(ApiHandler.class.getSimpleName());
private final Database db;
public ApiHandler(Database db) {
this.db = db;
}
@Override
public boolean doGet(String path, HttpExchange ex) throws IOException {
var params = queryParam(ex);
return switch (path) {
case "/event" -> loadEvent(ex,params);
case "/events/list" -> listEvents(ex,params);
default -> PathHandler.notFound(ex);
};
}
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.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));
}
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;
}
}
}
@@ -0,0 +1,22 @@
/* © SRSoftware 2024 */
package de.srsoftware.cal;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.tools.PathHandler;
import java.io.IOException;
public class IndexHandler extends PathHandler {
PathHandler staticPages;
public IndexHandler(PathHandler staticPages) {
this.staticPages = staticPages;
}
@Override
public boolean doGet(String path, HttpExchange ex) throws IOException {
switch (path) {
case "/":
return staticPages.doGet("/index", ex);
}
return super.doGet(path, ex);
}
}
@@ -0,0 +1,38 @@
/* © SRSoftware 2024 */
package de.srsoftware.cal;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.tools.PathHandler;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
public class StaticHandler extends PathHandler {
private final Optional<String> staticPath;
public StaticHandler(Optional<String> staticPath) {
this.staticPath = staticPath;
}
@Override
public boolean doGet(String path, HttpExchange ex) throws IOException {
if (!path.contains(".")) path += ".html";
if (path.startsWith("/")) path = path.substring(1);
var url = getClass().getClassLoader().getResource(path);
if (staticPath.isPresent()) {
var file = Path.of(staticPath.get()).resolve(path).toFile();
if (file.exists() && file.isFile()) url = file.toURI().toURL();
}
if (url == null) return PathHandler.notFound(ex);
var conn = url.openConnection();
var mime = conn.getContentType();
try (var input = conn.getInputStream()) {
var bos = new ByteArrayOutputStream();
input.transferTo(bos);
ex.getResponseHeaders().add(PathHandler.CONTENT_TYPE, mime);
return PathHandler.sendContent(ex, bos.toByteArray());
}
}
}
@@ -0,0 +1,22 @@
<html>
<head>
<meta charset="UTF-8" />
<title>SRSoftware OpenCloudCal</title>
<script src="/static/script/occ.js"></script>
</head>
<body>
<nav />
<h1>Loading…</h1>
<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>
<script>
document.addEventListener("DOMContentLoaded", function(event){
console.log("page loaded…");
loadEventData();
});
</script>
</body>
</html>
@@ -0,0 +1,29 @@
<html>
<head>
<meta charset="UTF-8" />
<title>SRSoftware OpenCloudCal</title>
<script src="/static/script/occ.js"></script>
</head>
<body>
<nav />
<div>
<h1>Event List</h1>
<table id="eventlist">
<tr>
<th>ID</th>
<th>Start</th>
<th>End</th>
<th>Location</th>
<th>Title</th>
<th>Tags</th>
</tr>
</table>
</div>
<script>
document.addEventListener("DOMContentLoaded", function(event){
console.log("page loaded…");
loadCurrentEvents();
});
</script>
</body>
</html>
@@ -0,0 +1,119 @@
var start = null;
var end = null;
function fetchEvents(start, end){
var path = '/api/events/list';
if (start) {
path += '?start='+start;
if (end) path+= '&end='+end;
}
fetch(path).then(handleEvents);
}
function addCell(row,content,id){
var a = document.createElement('a');
if (content){
a.href = '/static/event?id='+id;
a.innerHTML = content;
}
row.insertCell().appendChild(a);
}
function addRow(json){
var table = document.getElementById('eventlist');
var row = table.insertRow();
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);
row.appendChild(createTags(json.tags));
}
function createTags(tagList){
var td = document.createElement('td');
tagList.forEach(val => {
var btn = document.createElement('button');
btn.onclick = e => toggleTag(val);
btn.appendChild(document.createTextNode(val));
td.appendChild(btn);
td.appendChild(document.createTextNode(' '));
});
return td;
}
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);
}
}
function addDescription(json){
var desc = json.description ? json.description : '';
if (desc.indexOf('<')<0) desc = desc.replace(/\r?\n|\r/g,'<br/>');
document.getElementById('description').innerHTML = desc;
}
function attachmentList(json){
var attachments = document.getElementById('attachments');
attachments.innerHTML = '';
for (var attachment of json){
if (attachment.mime.startsWith('image')){
var img = document.createElement('img');
img.src = attachment.url;
attachments.appendChild(img);
}
}
}
function linkList(json){
var ul = document.createElement('ul');
for (var inner of json) {
var a = document.createElement('a');
a.href = inner.url;
a.innerHTML = inner.description;
var li = document.createElement('li')
li.appendChild(a);
ul.appendChild(li);
}
return ul;
}
async function handleEvents(response){
if (response.ok){
var json = await response.json();
json.forEach(addRow)
}
}
function loadCurrentEvents(){
if (start == null){
var now = new Date();
var year = now.getFullYear();
var month = now.getMonth() + 1;
start = year + '-' + (month < 10 ? '0' : '') + month;
fetchEvents(start,end);
}
}
function loadEventData(){
const urlParams = new URLSearchParams(window.location.search);
var id = urlParams.get('id');
if (id){
fetch('/api/event?id='+id).then(handleEventData)
}
}
function toggleTag(tag){
alert(`toggleTag(${tag})`);
}