Make calendar translate on the fly

Affects issues:
- #3668
- #3346
This commit is contained in:
Aurora Lahtela 2024-12-07 11:54:13 +02:00
parent 757bd550be
commit 33b0c1e7ce
7 changed files with 109 additions and 69 deletions

View File

@ -282,7 +282,7 @@ public class GraphJSONCreator {
return createUniqueAndNewJSON(lineGraphs, uniquePerDay, newPerDay, TimeUnit.HOURS.toMillis(1L));
}
public String serverCalendarJSON(ServerUUID serverUUID) {
public Map<String, Object> serverCalendarJSON(ServerUUID serverUUID) {
Database db = dbSystem.getDatabase();
long now = System.currentTimeMillis();
long twoYearsAgo = now - TimeUnit.DAYS.toMillis(730L);
@ -299,17 +299,17 @@ public class GraphJSONCreator {
NavigableMap<Long, Integer> sessionsPerDay = db.query(
SessionQueries.sessionCountPerDay(twoYearsAgo, now, timeZoneOffset, serverUUID)
);
return "{\"data\":" +
return Map.of("data",
graphs.calendar().serverCalendar(
uniquePerDay,
newPerDay,
playtimePerDay,
sessionsPerDay
).toCalendarSeries() +
",\"firstDay\":" + 1 + '}';
).getEntries(),
"firstDay", 1);
}
public String networkCalendarJSON() {
public Map<String, Object> networkCalendarJSON() {
Database db = dbSystem.getDatabase();
long now = System.currentTimeMillis();
long twoYearsAgo = now - TimeUnit.DAYS.toMillis(730L);
@ -326,14 +326,14 @@ public class GraphJSONCreator {
NavigableMap<Long, Integer> sessionsPerDay = db.query(
SessionQueries.sessionCountPerDay(twoYearsAgo, now, timeZoneOffset)
);
return "{\"data\":" +
return Map.of("data",
graphs.calendar().serverCalendar(
uniquePerDay,
newPerDay,
playtimePerDay,
sessionsPerDay
).toCalendarSeries() +
",\"firstDay\":" + 1 + '}';
).getEntries(),
"firstDay", 1);
}
public Map<String, Object> serverWorldPieJSONAsMap(ServerUUID serverUUID) {

View File

@ -27,17 +27,19 @@ import java.util.Optional;
public class CalendarEntry {
private final String title;
private final Serializable value;
private final Serializable start;
private Serializable end;
private String color;
private CalendarEntry(String title, Serializable start) {
private CalendarEntry(String title, Serializable value, Serializable start) {
this.title = title;
this.value = value;
this.start = start;
}
public static CalendarEntry of(String title, Serializable start) {
return new CalendarEntry(title, start);
public static CalendarEntry of(String title, Serializable value, Serializable start) {
return new CalendarEntry(title, value, start);
}
public CalendarEntry withEnd(Serializable end) {
@ -50,10 +52,14 @@ public class CalendarEntry {
return this;
}
public String getTitle() {
public Serializable getTitle() {
return title;
}
public Serializable getValue() {
return value;
}
public Serializable getStart() {
return start;
}
@ -70,6 +76,7 @@ public class CalendarEntry {
public String toString() {
return "{" +
"title:'" + title + '\'' +
", value:" + value +
", start:'" + start + '\'' +
(end != null ? ", end='" + end + '\'' : "") +
(color != null ? ", color='" + color + '\'' : "") +

View File

@ -68,7 +68,7 @@ public class CalendarFactory {
) {
return new ServerCalendar(
uniquePerDay, newPerDay, playtimePerDay, sessionsPerDay,
formatters.iso8601NoClockTZIndependentLong(), formatters.timeAmount(), theme, locale
formatters.iso8601NoClockTZIndependentLong(), theme
);
}
}

View File

@ -72,9 +72,8 @@ public class PlayerCalendar {
List<CalendarEntry> entries = new ArrayList<>();
entries.add(CalendarEntry
.of(locale.getString(HtmlLang.LABEL_REGISTERED) + ": " + year.apply(registered),
registered
).withColor(theme.getValue(ThemeVal.LIGHT_GREEN))
.of(HtmlLang.LABEL_REGISTERED.getKey(), registered, registered + timeZone.getOffset(registered))
.withColor(theme.getValue(ThemeVal.LIGHT_GREEN))
);
Map<String, List<FinishedSession>> sessionsByDay = getSessionsByDay();
@ -87,30 +86,30 @@ public class PlayerCalendar {
long playtime = sessions.stream().mapToLong(FinishedSession::getLength).sum();
entries.add(CalendarEntry
.of(locale.getString(HtmlLang.LABEL_PLAYTIME) + ": " + timeAmount.apply(playtime), day)
.of(HtmlLang.LABEL_PLAYTIME.getKey(), playtime, day)
.withColor(theme.getValue(ThemeVal.GREEN))
);
entries.add(CalendarEntry.of(locale.getString(HtmlLang.SIDE_SESSIONS) + ": " + sessionCount, day));
entries.add(CalendarEntry.of(HtmlLang.SIDE_SESSIONS.getKey(), sessionCount, day)
.withColor(theme.getValue(ThemeVal.TEAL)));
}
long fiveMinutes = TimeUnit.MINUTES.toMillis(5L);
for (FinishedSession session : allSessions) {
String length = timeAmount.apply(session.getLength());
long start = session.getStart();
long end = session.getEnd();
entries.add(CalendarEntry
.of(length + " " + locale.getString(HtmlLang.SESSION),
start + timeZone.getOffset(start))
.of(HtmlLang.SESSION.getKey(), session.getLength(), start + timeZone.getOffset(start))
.withEnd(end + timeZone.getOffset(end))
.withColor(theme.getValue(ThemeVal.TEAL))
);
for (PlayerKill kill : session.getExtraData(PlayerKills.class).map(PlayerKills::asList).orElseGet(ArrayList::new)) {
long time = kill.getDate();
String victim = kill.getVictim().getName();
entries.add(CalendarEntry
.of(locale.getString(HtmlLang.KILLED) + ": " + victim, time)
.of(HtmlLang.KILLED.getKey(), victim, time)
.withEnd(time + fiveMinutes)
.withColor(theme.getValue(ThemeVal.RED))
);

View File

@ -17,14 +17,11 @@
package com.djrapitops.plan.delivery.rendering.json.graphs.calendar;
import com.djrapitops.plan.delivery.formatting.Formatter;
import com.djrapitops.plan.settings.locale.Locale;
import com.djrapitops.plan.settings.locale.lang.HtmlLang;
import com.djrapitops.plan.settings.theme.Theme;
import com.djrapitops.plan.settings.theme.ThemeVal;
import java.util.Map;
import java.util.NavigableMap;
import java.util.SortedMap;
import java.util.*;
/**
* Utility for creating FullCalendar calendar event array on Player page.
@ -39,9 +36,7 @@ public class ServerCalendar {
private final SortedMap<Long, Long> playtimePerDay;
private final Formatter<Long> iso8601TZIndependent;
private final Formatter<Long> timeAmount;
private final Theme theme;
private final Locale locale;
ServerCalendar(
SortedMap<Long, Integer> uniquePerDay,
@ -49,37 +44,26 @@ public class ServerCalendar {
SortedMap<Long, Long> playtimePerDay,
NavigableMap<Long, Integer> sessionsPerDay,
Formatter<Long> iso8601TZIndependent,
Formatter<Long> timeAmount,
Theme theme,
Locale locale
Theme theme
) {
this.uniquePerDay = uniquePerDay;
this.newPerDay = newPerDay;
this.iso8601TZIndependent = iso8601TZIndependent;
this.timeAmount = timeAmount;
this.sessionsPerDay = sessionsPerDay;
this.playtimePerDay = playtimePerDay;
this.theme = theme;
this.locale = locale;
}
public String toCalendarSeries() {
StringBuilder series = new StringBuilder("[");
series.append("{\"title\": \"badcode\",\"start\":0}");
appendTimeZoneOffsetData(series);
return series.append("]").toString();
public List<CalendarEntry> getEntries() {
List<CalendarEntry> entries = new ArrayList<>();
appendUniquePlayers(entries);
appendNewPlayers(entries);
appendSessionCounts(entries);
appendPlaytime(entries);
return entries;
}
private void appendTimeZoneOffsetData(StringBuilder series) {
appendUniquePlayers(series);
appendNewPlayers(series);
appendSessionCounts(series);
appendPlaytime(series);
}
private void appendNewPlayers(StringBuilder series) {
private void appendNewPlayers(List<CalendarEntry> entries) {
for (Map.Entry<Long, Integer> entry : newPerDay.entrySet()) {
int newPlayers = entry.getValue();
if (newPlayers <= 0) {
@ -89,14 +73,12 @@ public class ServerCalendar {
Long key = entry.getKey();
String day = iso8601TZIndependent.apply(key);
series.append(",{\"title\": \"").append(locale.get(HtmlLang.NEW_CALENDAR)).append(" ").append(newPlayers)
.append("\",\"start\":\"").append(day)
.append("\",\"color\": \"").append(theme.getValue(ThemeVal.LIGHT_GREEN)).append('"')
.append("}");
entries.add(CalendarEntry.of(HtmlLang.NEW_CALENDAR.getKey(), newPlayers, day)
.withColor(theme.getValue(ThemeVal.LIGHT_GREEN)));
}
}
private void appendUniquePlayers(StringBuilder series) {
private void appendUniquePlayers(List<CalendarEntry> entries) {
for (Map.Entry<Long, Integer> entry : uniquePerDay.entrySet()) {
long uniquePlayers = entry.getValue();
if (uniquePlayers <= 0) {
@ -106,14 +88,11 @@ public class ServerCalendar {
Long key = entry.getKey();
String day = iso8601TZIndependent.apply(key);
series.append(",{\"title\": \"").append(locale.get(HtmlLang.UNIQUE_CALENDAR)).append(" ").append(uniquePlayers)
.append("\",\"start\":\"").append(day)
.append("\"}");
entries.add(CalendarEntry.of(HtmlLang.UNIQUE_CALENDAR.getKey(), uniquePlayers, day));
}
}
private void appendPlaytime(StringBuilder series) {
private void appendPlaytime(List<CalendarEntry> entries) {
for (Map.Entry<Long, Long> entry : playtimePerDay.entrySet()) {
long playtime = entry.getValue();
if (playtime <= 0) {
@ -122,14 +101,12 @@ public class ServerCalendar {
Long key = entry.getKey();
String day = iso8601TZIndependent.apply(key);
series.append(",{\"title\": \"").append(locale.get(HtmlLang.LABEL_PLAYTIME)).append(": ").append(timeAmount.apply(playtime))
.append("\",\"start\":\"").append(day)
.append("\",\"color\": \"").append(theme.getValue(ThemeVal.GREEN)).append('"')
.append("}");
entries.add(CalendarEntry.of(HtmlLang.LABEL_PLAYTIME.getKey(), playtime, day)
.withColor(theme.getValue(ThemeVal.GREEN)));
}
}
private void appendSessionCounts(StringBuilder series) {
private void appendSessionCounts(List<CalendarEntry> entries) {
for (Map.Entry<Long, Integer> entry : sessionsPerDay.entrySet()) {
int sessionCount = entry.getValue();
if (sessionCount <= 0) {
@ -138,10 +115,8 @@ public class ServerCalendar {
Long key = entry.getKey();
String day = iso8601TZIndependent.apply(key);
series.append(",{\"title\": \"").append(locale.get(HtmlLang.SIDE_SESSIONS)).append(": ").append(sessionCount)
.append("\",\"start\":\"").append(day)
.append("\",\"color\": \"").append(theme.getValue(ThemeVal.TEAL)).append('"')
.append("}");
entries.add(CalendarEntry.of(HtmlLang.SIDE_SESSIONS.getKey(), sessionCount, day)
.withColor(theme.getValue(ThemeVal.TEAL)));
}
}
}

View File

@ -1,8 +1,43 @@
import React from "react";
import FullCalendar from '@fullcalendar/react'
import dayGridPlugin from '@fullcalendar/daygrid'
import {useTranslation} from "react-i18next";
import {formatTimeAmount} from "../../util/format/TimeAmountFormat.js";
import {formatDate, useDatePreferences} from "../text/FormattedDate.jsx";
import {useTimePreferences} from "../text/FormattedTime.jsx";
const PlayerSessionCalendar = ({series, firstDay}) => {
const {t} = useTranslation();
const timePreferences = useTimePreferences();
const datePreferences = useDatePreferences();
const formatDateEasy = date => {
return formatDate(date, datePreferences.offset, datePreferences.pattern, false, datePreferences.recentDaysPattern, t);
}
const formatTitle = entry => {
switch (entry.title) {
case 'html.label.session':
return formatTimeAmount(timePreferences, entry.value) + ' ' + t(entry.title);
case 'html.label.playtime':
return t(entry.title) + ": " + formatTimeAmount(timePreferences, entry.value)
case 'html.label.registered':
return t(entry.title) + ": " + formatDateEasy(entry.value)
default:
return t(entry.title) + ": " + entry.value;
}
}
const actualSeries = series.map(entry => {
return {
title: formatTitle(entry),
start: entry.start,
end: entry.end,
color: entry.color
}
});
return (
<FullCalendar
plugins={[dayGridPlugin]}
@ -20,7 +55,7 @@ const PlayerSessionCalendar = ({series, firstDay}) => {
center: '',
right: 'dayGridMonth dayGridWeek dayGridDay today prev next'
}}
events={(_fetchInfo, successCallback) => successCallback(series)}
events={(_fetchInfo, successCallback) => successCallback(actualSeries)}
/>
)
}

View File

@ -5,6 +5,8 @@ import interactionPlugin from '@fullcalendar/interaction'
import {useTranslation} from "react-i18next";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faHandPointer} from "@fortawesome/free-regular-svg-icons";
import {useTimePreferences} from "../text/FormattedTime.jsx";
import {formatTimeAmount} from "../../util/format/TimeAmountFormat.js";
const ServerCalendar = ({series, firstDay, onSelect}) => {
const {t} = useTranslation();
@ -14,6 +16,28 @@ const ServerCalendar = ({series, firstDay, onSelect}) => {
top: "0.5rem",
right: "1rem"
};
const timePreferences = useTimePreferences();
const formatTitle = entry => {
switch (entry.title) {
case 'html.label.playtime':
return t(entry.title) + ": " + formatTimeAmount(timePreferences, entry.value)
case 'html.calendar.unique':
case 'html.calendar.new':
return t(entry.title) + " " + entry.value
default:
return t(entry.title) + ": " + entry.value;
}
}
const actualSeries = series.map(entry => {
return {
title: formatTitle(entry),
start: entry.start,
end: entry.end,
color: entry.color
}
});
return (
<div id={'server-calendar'}>
@ -38,7 +62,7 @@ const ServerCalendar = ({series, firstDay, onSelect}) => {
selectable={Boolean(onSelect)}
select={onSelect}
unselectAuto={true}
events={(_fetchInfo, successCallback) => successCallback(series)}
events={(_fetchInfo, successCallback) => successCallback(actualSeries)}
/>
</div>
)