feat: calendrier impérial et conversions calendaires

- calendrier impérial : fenêtre compacte (260px), affichage date
- commande /cal, auto-ouverture paramétrable dans les settings
- socket calendarSync : mise à jour temps réel pour les joueurs
- config MJ : année, jour (label live), heure:minute, planète
- boutons +1 jour et +1 heure (report 24h→jour, 365→1)
- conversions : Solomani, Vilani, Zhodani (Olympiade), Aslan, K'Kree, Hiver
- formules wiki.travellerrpg.com/Date_Conversion, section repliable
- fix: renderChatInput /cal trop large → exact match
- fix: fenêtre refermable (onClose nullifie la référence)
- fix: socket joueur lit data direct au lieu de readCalendar()
This commit is contained in:
2026-07-23 23:28:49 +02:00
parent 5a4d4c45dc
commit 00bcc775fe
9 changed files with 723 additions and 2 deletions
+208
View File
@@ -0,0 +1,208 @@
import { CalendarApp } from './CalendarApp.js';
import { CalendarConfigDialog } from './CalendarConfigDialog.js';
import { getDefaultCalendar } from './calendarDate.js';
const MODULE_ID = 'mgt2-compendium-amiral-denisov';
const ChatLogV2 = foundry.applications.sidebar.tabs.ChatLog;
let _calendarApp = null;
function readCalendar() {
return {
year: game.settings.get(MODULE_ID, 'calendarYear'),
day: game.settings.get(MODULE_ID, 'calendarDay'),
hour: game.settings.get(MODULE_ID, 'calendarHour'),
minute: game.settings.get(MODULE_ID, 'calendarMinute'),
planet: game.settings.get(MODULE_ID, 'calendarPlanet'),
};
}
async function writeCalendar(data) {
await game.settings.set(MODULE_ID, 'calendarYear', data.year);
await game.settings.set(MODULE_ID, 'calendarDay', data.day);
await game.settings.set(MODULE_ID, 'calendarHour', data.hour);
await game.settings.set(MODULE_ID, 'calendarMinute', data.minute);
await game.settings.set(MODULE_ID, 'calendarPlanet', data.planet);
}
function applyCalendar(data) {
if (_calendarApp) {
_calendarApp.updateDisplay(data);
}
}
async function broadcastCalendar(data) {
if (!game.socket?.emit) return;
game.socket.emit(`module.${MODULE_ID}`, {
_type: 'calendarSync',
...data,
});
}
function openCalendarApp() {
if (_calendarApp) {
_calendarApp.bringToTop();
return;
}
const data = readCalendar();
_calendarApp = new CalendarApp({
onConfig: () => openCalendarConfig(),
onClose: () => { _calendarApp = null; },
});
_calendarApp.updateDisplay(data);
_calendarApp.render({ force: true });
}
function openCalendarConfig() {
const data = readCalendar();
const dialog = new CalendarConfigDialog({
data,
onApply: async (newData) => {
await writeCalendar(newData);
applyCalendar(newData);
broadcastCalendar(newData);
dialog.close();
},
});
dialog.render({ force: true });
}
function registerCalCommand() {
if (!ChatLogV2?.CHAT_COMMANDS) return;
ChatLogV2.CHAT_COMMANDS.cal = {
rgx: /^\/cal(?:\s+(.*))?$/i,
fn: () => {
openCalendarApp();
return false;
},
};
console.log(`${MODULE_ID} | Commande /cal enregistrée via ChatLog.CHAT_COMMANDS`);
}
Hooks.once('init', () => {
const def = getDefaultCalendar();
game.settings.register(MODULE_ID, 'calendarYear', {
name: 'Année impériale',
scope: 'world',
config: false,
type: Number,
default: def.year,
});
game.settings.register(MODULE_ID, 'calendarDay', {
name: 'Jour impérial (1-365)',
scope: 'world',
config: false,
type: Number,
default: def.day,
});
game.settings.register(MODULE_ID, 'calendarHour', {
name: 'Heure locale',
scope: 'world',
config: false,
type: Number,
default: def.hour,
});
game.settings.register(MODULE_ID, 'calendarMinute', {
name: 'Minute locale',
scope: 'world',
config: false,
type: Number,
default: def.minute,
});
game.settings.register(MODULE_ID, 'calendarPlanet', {
name: 'Planète',
scope: 'world',
config: false,
type: String,
default: def.planet,
});
game.settings.register(MODULE_ID, 'calendarAutoOpen', {
name: 'Calendrier impérial : auto-ouverture',
hint: 'Ouvre automatiquement la fenêtre du calendrier au démarrage',
scope: 'world',
config: true,
type: Boolean,
default: true,
});
const loadTemplatesFn = foundry.applications?.handlebars?.loadTemplates || loadTemplates;
if (loadTemplatesFn) {
loadTemplatesFn([
`modules/${MODULE_ID}/templates/calendar-app.hbs`,
`modules/${MODULE_ID}/templates/calendar-config.hbs`,
]);
}
registerCalCommand();
console.log(`${MODULE_ID} | Calendrier impérial initialisé`);
});
Hooks.once('ready', () => {
game.socket.on(`module.${MODULE_ID}`, (data) => {
if (game.user?.isGM) return;
if (data?._type !== 'calendarSync') return;
if (!_calendarApp) {
const { year, day, hour, minute, planet } = data;
_calendarApp = new CalendarApp({
onClose: () => { _calendarApp = null; },
});
_calendarApp.updateDisplay({ year, day, hour, minute, planet });
_calendarApp.render({ force: true });
return;
}
_calendarApp.updateDisplay(data);
});
if (game.settings.get(MODULE_ID, 'calendarAutoOpen')) {
openCalendarApp();
}
});
Hooks.on('preCreateChatMessage', (message, data, options) => {
const content = message.content?.trim()?.toLowerCase();
if (content === '/cal' || content?.startsWith('/cal ')) {
openCalendarApp();
return false;
}
});
Hooks.on('chatMessage', (...args) => {
let message;
if (args[0]?.content !== undefined) {
message = args[0].content;
} else if (typeof args[1] === 'string') {
message = args[1];
} else {
return;
}
const trimmed = message?.trim()?.toLowerCase();
if (trimmed === '/cal' || trimmed?.startsWith('/cal ')) {
openCalendarApp();
return false;
}
});
Hooks.on('renderChatInput', (app, html, data) => {
let input;
if (html?.element) {
input = $(html.element).find('textarea[name="content"]');
} else if (html?.find) {
input = html.find('textarea[name="content"]');
} else {
input = $(html).find('textarea[name="content"]');
}
if (input.data('mgt2-cal-listener')) return;
input.data('mgt2-cal-listener', true);
input.on('keydown', (event) => {
if (event.key === 'Enter' && !event.shiftKey) {
const content = input.val()?.trim();
if (content === '/cal' || content?.startsWith('/cal ')) {
event.preventDefault();
event.stopImmediatePropagation();
openCalendarApp();
input.val('');
}
}
});
});