feat: Add a new Foundry VTT module for tracking and displaying session numbers with GM controls and auto-increment functionality.
This commit is contained in:
parent
ad704b5fa2
commit
44b80af3d0
5 changed files with 320 additions and 0 deletions
26
module.json
Normal file
26
module.json
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"id": "fvtt-session-tracker",
|
||||||
|
"title": "Session Tracker",
|
||||||
|
"description": "A lightweight, premium-looking session tracker for Foundry VTT V13.",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "CPTN Cosmo",
|
||||||
|
"email": "cptncosmo@gmail.com",
|
||||||
|
"url": "https://github.com/cptn-cosmo",
|
||||||
|
"discord": "cptn_cosmo"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibility": {
|
||||||
|
"minimum": "13",
|
||||||
|
"verified": "13"
|
||||||
|
},
|
||||||
|
"esmodules": [
|
||||||
|
"scripts/main.js"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"styles/session-tracker.css"
|
||||||
|
],
|
||||||
|
"url": "https://github.com/cptn-cosmo/fvtt-session-tracker",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
90
scripts/main.js
Normal file
90
scripts/main.js
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
import { SessionTrackerApp } from "./session-tracker-app.js";
|
||||||
|
|
||||||
|
Hooks.once("init", () => {
|
||||||
|
console.log("Session Tracker | Initializing");
|
||||||
|
|
||||||
|
// Register Session Count
|
||||||
|
game.settings.register("fvtt-session-tracker", "sessionCount", {
|
||||||
|
name: "Current Session Number",
|
||||||
|
hint: "The current session number displayed on the tracker.",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
onChange: () => {
|
||||||
|
if (SessionTrackerApp.instance) SessionTrackerApp.instance.render(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register Auto-Increment Toggle
|
||||||
|
game.settings.register("fvtt-session-tracker", "autoIncrement", {
|
||||||
|
name: "Automate Session Tracking",
|
||||||
|
hint: "Automatically increment the session count when the selected user logs in.",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register Trigger User
|
||||||
|
game.settings.register("fvtt-session-tracker", "triggerUser", {
|
||||||
|
name: "Trigger User",
|
||||||
|
hint: "Selecting this user will trigger the session increment when they log in.",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
choices: () => {
|
||||||
|
const users = game.users.reduce((acc, u) => {
|
||||||
|
acc[u.id] = u.name;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register tracker position (internal)
|
||||||
|
game.settings.register("fvtt-session-tracker", "position", {
|
||||||
|
scope: "client",
|
||||||
|
config: false,
|
||||||
|
type: Object,
|
||||||
|
default: { top: 10, left: 120 }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Register visibility toggle for players
|
||||||
|
game.settings.register("fvtt-session-tracker", "showTracker", {
|
||||||
|
name: "Show Session Tracker",
|
||||||
|
hint: "Whether to display the session tracker on your canvas.",
|
||||||
|
scope: "client",
|
||||||
|
config: true,
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
onChange: (value) => {
|
||||||
|
if (SessionTrackerApp.instance) {
|
||||||
|
if (value) SessionTrackerApp.instance.render(true);
|
||||||
|
else SessionTrackerApp.instance.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Hooks.once("ready", () => {
|
||||||
|
// Initialize the app
|
||||||
|
SessionTrackerApp.initialize();
|
||||||
|
|
||||||
|
// Logic for auto-incrementing
|
||||||
|
if (game.user.isGM) {
|
||||||
|
Hooks.on("userConnected", (user, connected) => {
|
||||||
|
if (!connected) return;
|
||||||
|
|
||||||
|
const isAuto = game.settings.get("fvtt-session-tracker", "autoIncrement");
|
||||||
|
const targetUser = game.settings.get("fvtt-session-tracker", "triggerUser");
|
||||||
|
|
||||||
|
if (isAuto && user.id === targetUser) {
|
||||||
|
const current = game.settings.get("fvtt-session-tracker", "sessionCount");
|
||||||
|
game.settings.set("fvtt-session-tracker", "sessionCount", current + 1);
|
||||||
|
ui.notifications.info(`Session Tracker | User ${user.name} logged in. Incrementing to session ${current + 1}.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
106
scripts/session-tracker-app.js
Normal file
106
scripts/session-tracker-app.js
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
|
|
||||||
|
export class SessionTrackerApp extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
static instance;
|
||||||
|
|
||||||
|
constructor(options = {}) {
|
||||||
|
super(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
id: "session-tracker-app",
|
||||||
|
tag: "aside",
|
||||||
|
classes: ["session-tracker"],
|
||||||
|
window: {
|
||||||
|
frame: false,
|
||||||
|
positioned: true,
|
||||||
|
},
|
||||||
|
position: {
|
||||||
|
width: "auto",
|
||||||
|
height: "auto",
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
increment: SessionTrackerApp.#onIncrement,
|
||||||
|
decrement: SessionTrackerApp.#onDecrement,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
content: {
|
||||||
|
template: "modules/fvtt-session-tracker/templates/session-tracker.hbs",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static initialize() {
|
||||||
|
this.instance = new SessionTrackerApp();
|
||||||
|
if (!game.settings.get("fvtt-session-tracker", "showTracker")) return;
|
||||||
|
const pos = game.settings.get("fvtt-session-tracker", "position");
|
||||||
|
this.instance.render(true, { position: pos });
|
||||||
|
}
|
||||||
|
|
||||||
|
async _prepareContext(options) {
|
||||||
|
return {
|
||||||
|
sessionNumber: game.settings.get("fvtt-session-tracker", "sessionCount"),
|
||||||
|
isGM: game.user.isGM
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onIncrement(event, target) {
|
||||||
|
if (!game.user.isGM) return;
|
||||||
|
const current = game.settings.get("fvtt-session-tracker", "sessionCount");
|
||||||
|
await game.settings.set("fvtt-session-tracker", "sessionCount", current + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static async #onDecrement(event, target) {
|
||||||
|
if (!game.user.isGM) return;
|
||||||
|
const current = game.settings.get("fvtt-session-tracker", "sessionCount");
|
||||||
|
if (current <= 1) return;
|
||||||
|
await game.settings.set("fvtt-session-tracker", "sessionCount", current - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drag and drop support for positioning
|
||||||
|
_onRender(context, options) {
|
||||||
|
if (!game.user.isGM) return;
|
||||||
|
|
||||||
|
const dragHandle = this.element;
|
||||||
|
let isDragging = false;
|
||||||
|
let startX, startY, startLeft, startTop;
|
||||||
|
|
||||||
|
dragHandle.addEventListener('mousedown', (e) => {
|
||||||
|
if (e.button !== 0) return; // Only left click
|
||||||
|
isDragging = true;
|
||||||
|
startX = e.clientX;
|
||||||
|
startY = e.clientY;
|
||||||
|
const rect = this.element.getBoundingClientRect();
|
||||||
|
startLeft = rect.left;
|
||||||
|
startTop = rect.top;
|
||||||
|
|
||||||
|
this.element.style.cursor = 'grabbing';
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('mousemove', (e) => {
|
||||||
|
if (!isDragging) return;
|
||||||
|
const dx = e.clientX - startX;
|
||||||
|
const dy = e.clientY - startY;
|
||||||
|
|
||||||
|
const newLeft = startLeft + dx;
|
||||||
|
const newTop = startTop + dy;
|
||||||
|
|
||||||
|
this.element.style.left = `${newLeft}px`;
|
||||||
|
this.element.style.top = `${newTop}px`;
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('mouseup', () => {
|
||||||
|
if (!isDragging) return;
|
||||||
|
isDragging = false;
|
||||||
|
this.element.style.cursor = 'move';
|
||||||
|
|
||||||
|
// Save position
|
||||||
|
const rect = this.element.getBoundingClientRect();
|
||||||
|
game.settings.set("fvtt-session-tracker", "position", {
|
||||||
|
top: rect.top,
|
||||||
|
left: rect.left
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
83
styles/session-tracker.css
Normal file
83
styles/session-tracker.css
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@400;700&display=swap');
|
||||||
|
|
||||||
|
.session-tracker {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 100;
|
||||||
|
font-family: 'Outfit', sans-serif;
|
||||||
|
pointer-events: all;
|
||||||
|
user-select: none;
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-tracker-content {
|
||||||
|
background: rgba(0, 0, 0, 0.45);
|
||||||
|
backdrop-filter: blur(12px);
|
||||||
|
-webkit-backdrop-filter: blur(12px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-tracker-content:hover {
|
||||||
|
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.5);
|
||||||
|
background: rgba(0, 0, 0, 0.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-label {
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 10px;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
color: rgba(255, 255, 255, 0.6);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-number {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
|
||||||
|
margin-top: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
padding-top: 8px;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-controls button {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 6px;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-controls button:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-controls button:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
background: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-controls button i {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
15
templates/session-tracker.hbs
Normal file
15
templates/session-tracker.hbs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
<div class="session-tracker-content">
|
||||||
|
<div class="session-label">Session</div>
|
||||||
|
<div class="session-number">{{sessionNumber}}</div>
|
||||||
|
|
||||||
|
{{#if isGM}}
|
||||||
|
<div class="session-controls">
|
||||||
|
<button type="button" data-action="decrement" title="Decrease Session Number">
|
||||||
|
<i class="fas fa-minus"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" data-action="increment" title="Increase Session Number">
|
||||||
|
<i class="fas fa-plus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue