Skip to content

Commit 1781529

Browse files
committed
v 3.3.1
1 parent 8586b9f commit 1781529

3 files changed

Lines changed: 85 additions & 40 deletions

File tree

module.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"id": "investigation-board",
33
"title": "Investigation Board",
44
"description": "<p>A Foundry VTT module that lets everyone create, edit, and move sticky and photo notes on the scene as collaborative investigation tools.</p>",
5-
"version": "3.3.0",
5+
"version": "3.3.1",
66
"socket": true,
77
"authors": [
88
{

scripts/investigation-board-hud.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,11 @@ export class InvestigationBoardHUD extends HandlebarsApplicationMixin(BasePlacea
8080
html.querySelector(".delete-note")?.addEventListener("click", async (event) => {
8181
event.preventDefault();
8282

83-
const confirm = await Dialog.confirm({
84-
title: "Delete Note",
83+
const confirm = await foundry.applications.api.DialogV2.confirm({
84+
window: { title: "Delete Note" },
8585
content: "<p>Are you sure you want to delete this note?</p>",
86-
yes: () => true,
87-
no: () => false
86+
rejectClose: false,
87+
modal: true
8888
});
8989

9090
if (confirm) {

scripts/investigation-board.js

Lines changed: 80 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -684,10 +684,13 @@ class CustomDrawingSheet extends DrawingConfig {
684684
*/
685685
class NotePreviewer extends HandlebarsApplicationMixin(ApplicationV2) {
686686
constructor(document, options = {}) {
687+
// Ensure singleton instance per document
688+
options.id = `note-preview-${document.id}`;
687689
super(options);
688690
this.document = document;
689691
this.localSound = null;
690692
this.globalSoundActive = false;
693+
this.audioPollInterval = null;
691694
}
692695

693696
static DEFAULT_OPTIONS = {
@@ -771,6 +774,26 @@ class NotePreviewer extends HandlebarsApplicationMixin(ApplicationV2) {
771774
localAudio.addEventListener("ended", () => {
772775
if (!this.globalSoundActive) cassette.classList.remove("playing");
773776
});
777+
778+
// Poll for external playback (e.g. from canvas context menu) to sync animation
779+
const audioPath = this.document.flags[MODULE_ID]?.audioPath;
780+
if (audioPath) {
781+
this.audioPollInterval = setInterval(() => {
782+
// Check if any sound with this source is currently playing
783+
const isPlaying = Array.from(game.audio.playing.values()).some(s => s.src === audioPath && s.playing);
784+
785+
if (isPlaying) {
786+
if (!cassette.classList.contains("playing")) {
787+
cassette.classList.add("playing");
788+
}
789+
} else {
790+
// Only remove if not locally playing (checked via audio element) and not globally active state
791+
if (localAudio.paused && !this.globalSoundActive && cassette.classList.contains("playing")) {
792+
cassette.classList.remove("playing");
793+
}
794+
}
795+
}, 500);
796+
}
774797
}
775798

776799
// Global Play Button (GM only)
@@ -852,9 +875,32 @@ class NotePreviewer extends HandlebarsApplicationMixin(ApplicationV2) {
852875
}
853876
});
854877
});
878+
879+
// Handle auto-play options passed from Context Menu
880+
if (options.autoplay) {
881+
if (localAudio && localAudio.paused) {
882+
// Small delay to ensure DOM is ready and constraints are met
883+
setTimeout(() => {
884+
localAudio.play().catch(e => console.warn("Investigation Board: Auto-play blocked by browser policy", e));
885+
}, 100);
886+
}
887+
}
888+
889+
if (options.autobroadcast && playGlobalBtn) {
890+
// Small delay to ensure logic is ready
891+
setTimeout(() => {
892+
if (!playGlobalBtn.classList.contains("active")) {
893+
playGlobalBtn.click();
894+
}
895+
}, 100);
896+
}
855897
}
856898

857899
async _onClose(options) {
900+
if (this.audioPollInterval) {
901+
clearInterval(this.audioPollInterval);
902+
this.audioPollInterval = null;
903+
}
858904
if (this.localSound) {
859905
this.localSound.stop();
860906
this.localSound = null;
@@ -1045,14 +1091,22 @@ class CustomDrawing extends Drawing {
10451091

10461092
// Media-specific options
10471093
if (noteData.type === "media" && noteData.audioPath) {
1048-
// Local Play/Stop
1049-
const isLocalPlaying = Array.from(game.audio.playing.values()).some(s => s.src === noteData.audioPath);
1094+
const appId = `note-preview-${this.document.id}`;
1095+
const app = foundry.applications.instances.get(appId);
1096+
const audioEl = app?.element?.querySelector('.local-audio-player');
1097+
const isAppPlaying = audioEl && !audioEl.paused;
1098+
const isLegacyPlaying = Array.from(game.audio.playing.values()).some(s => s.src === noteData.audioPath);
1099+
const isPlaying = isAppPlaying || isLegacyPlaying;
1100+
10501101
const playMeOption = document.createElement('div');
10511102

1052-
if (isLocalPlaying) {
1103+
if (isPlaying) {
10531104
playMeOption.innerHTML = '<i class="fas fa-stop"></i> Stop for Me';
10541105
playMeOption.onclick = (e) => {
10551106
e.stopPropagation();
1107+
// Stop App Audio
1108+
if (audioEl) audioEl.pause();
1109+
// Stop Legacy Audio
10561110
const sounds = Array.from(game.audio.playing.values()).filter(s => s.src === noteData.audioPath);
10571111
sounds.forEach(s => s.stop());
10581112
menu.remove();
@@ -1061,11 +1115,8 @@ class CustomDrawing extends Drawing {
10611115
playMeOption.innerHTML = '<i class="fas fa-volume-up"></i> Play for Me';
10621116
playMeOption.onclick = (e) => {
10631117
e.stopPropagation();
1064-
// Check if already playing locally
1065-
if (Array.from(game.audio.playing.values()).some(s => s.src === noteData.audioPath && s.playing)) {
1066-
return;
1067-
}
1068-
game.audio.play(noteData.audioPath, { volume: 0.8 });
1118+
// Open Preview with autoplay
1119+
new NotePreviewer(this.document).render(true, { autoplay: true });
10691120
menu.remove();
10701121
};
10711122
}
@@ -1080,12 +1131,22 @@ class CustomDrawing extends Drawing {
10801131
playAllOption.innerHTML = '<i class="fas fa-stop"></i> Stop for All';
10811132
playAllOption.onclick = (e) => {
10821133
e.stopPropagation();
1134+
// Try to use App control first
1135+
if (app) {
1136+
const globalBtn = app.element.querySelector('.global-toggle');
1137+
if (globalBtn && globalBtn.classList.contains('active')) {
1138+
globalBtn.click();
1139+
menu.remove();
1140+
return;
1141+
}
1142+
}
1143+
1144+
// Fallback to manual socket stop
10831145
if (socket) {
10841146
socket.emit(SOCKET_NAME, {
10851147
action: "stopAudio",
10861148
audioPath: noteData.audioPath
10871149
});
1088-
// Stop locally too (GM is a client)
10891150
const sound = activeGlobalSounds.get(noteData.audioPath);
10901151
if (sound) {
10911152
sound.stop();
@@ -1098,22 +1159,8 @@ class CustomDrawing extends Drawing {
10981159
playAllOption.innerHTML = '<i class="fas fa-broadcast-tower"></i> Play for All';
10991160
playAllOption.onclick = (e) => {
11001161
e.stopPropagation();
1101-
// Check if already playing globally
1102-
if (activeGlobalSounds.has(noteData.audioPath) && activeGlobalSounds.get(noteData.audioPath).playing) {
1103-
return;
1104-
}
1105-
1106-
if (socket) {
1107-
socket.emit(SOCKET_NAME, {
1108-
action: "playAudio",
1109-
audioPath: noteData.audioPath
1110-
});
1111-
(async () => {
1112-
const sound = await game.audio.play(noteData.audioPath, { volume: 0.8 });
1113-
if (sound) activeGlobalSounds.set(noteData.audioPath, sound);
1114-
})();
1115-
ui.notifications.info(`Playing for everyone: ${noteData.audioPath}`);
1116-
}
1162+
// Unified: Open Preview and broadcast
1163+
new NotePreviewer(this.document).render(true, { autobroadcast: true });
11171164
menu.remove();
11181165
};
11191166
}
@@ -1138,12 +1185,11 @@ class CustomDrawing extends Drawing {
11381185
e.stopPropagation();
11391186
menu.remove();
11401187

1141-
const confirm = await Dialog.confirm({
1142-
title: "Remove All Connections",
1188+
const confirm = await foundry.applications.api.DialogV2.confirm({
1189+
window: { title: "Remove All Connections" },
11431190
content: `<p>Are you sure you want to remove ALL yarn connections connected to this note (incoming and outgoing)?</p>`,
1144-
yes: () => true,
1145-
no: () => false,
1146-
defaultYes: false
1191+
rejectClose: false,
1192+
modal: true
11471193
});
11481194

11491195
if (confirm) {
@@ -1254,12 +1300,11 @@ class CustomDrawing extends Drawing {
12541300
e.stopPropagation();
12551301
menu.remove();
12561302

1257-
const confirm = await Dialog.confirm({
1258-
title: "Delete Note",
1303+
const confirm = await foundry.applications.api.DialogV2.confirm({
1304+
window: { title: "Delete Note" },
12591305
content: `<p>Are you sure you want to delete this note?</p>`,
1260-
yes: () => true,
1261-
no: () => false,
1262-
defaultYes: false
1306+
rejectClose: false,
1307+
modal: true
12631308
});
12641309

12651310
if (confirm) {

0 commit comments

Comments
 (0)