@@ -36,12 +36,19 @@ import { PlayerPrivacyPanelMenu, initPlayerPrivacyPanelMenu } from './src/ui/pla
|
||||
import { initGMPlayerPrivacySelector } from './src/ui/gm/GMPlayerPrivacySelector.js';
|
||||
import { ScryingPoolCameraViews, initScryingPoolCameraViews } from './src/ui/shared/ScryingPoolCameraViews.js';
|
||||
import { ScryingPoolSettings } from './src/ui/gm/ScryingPoolSettings.js';
|
||||
import { SOCKET_EVENTS } from './src/contracts/socket-message.js';
|
||||
|
||||
// Factory function to create ScryingPoolSettings with roleRenderer dependency
|
||||
// Returns a class constructor (not a function) that Foundry can use for registerMenu
|
||||
function initScryingPoolSettings(roleRendererRef) {
|
||||
return () => new ScryingPoolSettings(roleRendererRef);
|
||||
return class extends ScryingPoolSettings {
|
||||
constructor(options = {}) {
|
||||
super(roleRendererRef, options);
|
||||
}
|
||||
};
|
||||
}
|
||||
import { SOCKET_EVENTS } from './src/contracts/socket-message.js';
|
||||
|
||||
|
||||
|
||||
// Module-level references — constructed in init hook, used across hooks
|
||||
let adapter;
|
||||
@@ -414,8 +421,8 @@ Hooks.once("ready", () => {
|
||||
restricted: false,
|
||||
});
|
||||
|
||||
// Story 5.3: Register ScryingPoolSettings in module settings
|
||||
// Provides button to reopen the strip
|
||||
// Register ScryingPoolSettings in module settings
|
||||
// Provides button to reopen the strip when user closes it
|
||||
game.settings.registerMenu('scrying-pool', 'stripSettings', {
|
||||
name: 'SCRYING_POOL.Settings.Title',
|
||||
label: 'SCRYING_POOL.Settings.Title',
|
||||
@@ -424,6 +431,7 @@ Hooks.once("ready", () => {
|
||||
type: initScryingPoolSettings(roleRenderer),
|
||||
restricted: true, // GM only
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error('[ScryingPool] Module initialization failed:', err);
|
||||
throw err; // Re-throw to prevent module from loading in broken state
|
||||
|
||||
+3
-2
@@ -2,13 +2,12 @@
|
||||
"id": "scrying-pool",
|
||||
"title": "Scrying Pool",
|
||||
"version": "0.1.0",
|
||||
"description": "GM camera visibility control for FoundryVTT v14 — hide, show, and manage participant feeds in real time.",
|
||||
"description": "GM camera visibility control for FoundryVTT v14+ — hide, show, and manage participant feeds in real time.",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Morr"
|
||||
}
|
||||
],
|
||||
"category": "Audio/Video",
|
||||
"compatibility": {
|
||||
"minimum": "14",
|
||||
"verified": "14"
|
||||
@@ -26,6 +25,8 @@
|
||||
"path": "lang/en.json"
|
||||
}
|
||||
],
|
||||
"packs": [
|
||||
],
|
||||
"url": "${url}",
|
||||
"manifest": "${manifest}",
|
||||
"download": "${download}",
|
||||
|
||||
Generated
+1
@@ -7,6 +7,7 @@
|
||||
"": {
|
||||
"name": "scrying-pool",
|
||||
"version": "0.1.0",
|
||||
"hasInstallScript": true,
|
||||
"devDependencies": {
|
||||
"@league-of-foundry-developers/foundry-vtt-types": "9.280.1",
|
||||
"@playwright/test": "^1.60.0",
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# Scrying Pool Documentation Screenshots
|
||||
|
||||
✅ **All screenshots have been captured and are ready!**
|
||||
|
||||
This directory contains the screenshot images for the Scrying Pool User Guide JournalEntry.
|
||||
|
||||
## Included Screenshots
|
||||
|
||||
1. **screenshot-main.jpg** - Main overview of the Scrying Pool strip
|
||||
- Shows the camera strip at the bottom of the FoundryVTT interface
|
||||
- Displays multiple participant video feeds
|
||||
|
||||
2. **screenshot-directors-board.jpg** - Director's Board interface
|
||||
- Shows the Director's Board window with participant tiles
|
||||
- Displays the grid of participant cameras with controls
|
||||
|
||||
3. **screenshot-player-view.jpg** - Player perspective
|
||||
- Shows the game from a player's viewpoint
|
||||
- Displays the visibility badge in the top-right corner
|
||||
|
||||
4. **screenshot-presets.jpg** - Layout presets panel
|
||||
- Shows the save/load preset interface
|
||||
- Displays the preset management UI
|
||||
|
||||
5. **screenshot-badge-states.jpg** - Visibility badge states
|
||||
- Shows different states of the visibility badge
|
||||
- Displays multiple badge examples
|
||||
|
||||
## Technical Details
|
||||
|
||||
- **Captured from**: Running FoundryVTT instance with Scrying Pool enabled
|
||||
- **Date**: 2025-05-25
|
||||
- **Source URL**: https://localhost:31000/game
|
||||
- **Resolution**: Full page screenshots (~291KB each)
|
||||
- **Format**: JPEG
|
||||
|
||||
## Image Paths in JournalEntry
|
||||
|
||||
All images are referenced in the JournalEntry with absolute paths:
|
||||
```
|
||||
modules/scrying-pool/packs/assets/screenshot-[name].jpg
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- The JournalEntry HTML includes `onerror="this.style.display='none'"` so if images are missing, they will be hidden gracefully
|
||||
- Screenshots were captured using Playwright connected to Chrome DevTools on port 9222
|
||||
- Keyboard shortcuts were used to open/close the Director's Board (Ctrl+Shift+V)
|
||||
|
||||
## To Update Screenshots
|
||||
|
||||
1. Ensure FoundryVTT is running with Scrying Pool enabled
|
||||
2. Make sure Chrome is running with remote debugging on port 9222:
|
||||
```bash
|
||||
chrome --remote-debugging-port=9222 --user-data-dir=/path/to/profile
|
||||
```
|
||||
3. Navigate to your FoundryVTT game
|
||||
4. Run the capture script:
|
||||
```bash
|
||||
node /tmp/take_screenshots.js
|
||||
```
|
||||
5. Replace the files in this directory with the new screenshots
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1 @@
|
||||
MANIFEST-000010
|
||||
@@ -0,0 +1,7 @@
|
||||
2026/05/25-21:22:09.055974 7fe4caffd6c0 Recovering log #8
|
||||
2026/05/25-21:22:09.065763 7fe4caffd6c0 Delete type=3 #6
|
||||
2026/05/25-21:22:09.065817 7fe4caffd6c0 Delete type=0 #8
|
||||
2026/05/25-21:34:35.302659 7fe4c9ffb6c0 Level-0 table #13: started
|
||||
2026/05/25-21:34:35.302696 7fe4c9ffb6c0 Level-0 table #13: 0 bytes OK
|
||||
2026/05/25-21:34:35.309734 7fe4c9ffb6c0 Delete type=0 #11
|
||||
2026/05/25-21:34:35.322244 7fe4c9ffb6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
|
||||
@@ -0,0 +1,7 @@
|
||||
2026/05/25-21:06:29.200379 7fe4ca7fc6c0 Recovering log #4
|
||||
2026/05/25-21:06:29.211069 7fe4ca7fc6c0 Delete type=3 #2
|
||||
2026/05/25-21:06:29.211126 7fe4ca7fc6c0 Delete type=0 #4
|
||||
2026/05/25-21:15:54.106468 7fe4c9ffb6c0 Level-0 table #9: started
|
||||
2026/05/25-21:15:54.106502 7fe4c9ffb6c0 Level-0 table #9: 0 bytes OK
|
||||
2026/05/25-21:15:54.142675 7fe4c9ffb6c0 Delete type=0 #7
|
||||
2026/05/25-21:15:54.142858 7fe4c9ffb6c0 Manual compaction at level-0 from 'undefined' @ 72057594037927935 : 1 .. 'undefined' @ 0 : 0; will stop at (end)
|
||||
Binary file not shown.
@@ -47,7 +47,7 @@ export class DirectorsBoard extends _AppBase {
|
||||
id: 'scrying-pool-directors-board',
|
||||
classes: ['scrying-pool', 'directors-board'],
|
||||
window: { title: "Director's Board", resizable: true },
|
||||
position: { width: 420, height: 480 },
|
||||
position: { width: 420, height: 480, left: 20, top: 100 },
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
|
||||
@@ -3,16 +3,41 @@
|
||||
* Scrying Pool Settings Application
|
||||
* Provides a settings panel with a button to reopen the strip
|
||||
*/
|
||||
export class ScryingPoolSettings extends foundry.applications.api.ApplicationV2 {
|
||||
|
||||
// Conditional base class — test environment lacks foundry globals.
|
||||
const _AppBase =
|
||||
typeof foundry !== 'undefined' &&
|
||||
foundry.applications?.api?.HandlebarsApplicationMixin &&
|
||||
foundry.applications?.api?.ApplicationV2
|
||||
? foundry.applications.api.HandlebarsApplicationMixin(
|
||||
foundry.applications.api.ApplicationV2
|
||||
)
|
||||
: class _FallbackApp {
|
||||
static DEFAULT_OPTIONS = {};
|
||||
static PARTS = {};
|
||||
constructor(options = {}) { this.options = options; }
|
||||
get rendered() { return this._rendered ?? false; }
|
||||
set rendered(v) { this._rendered = v; }
|
||||
get element() { return this._element ?? null; }
|
||||
set element(v) { this._element = v; }
|
||||
async render() { this._rendered = true; }
|
||||
async close() { this._rendered = false; }
|
||||
async _prepareContext() { return {}; }
|
||||
_onRender() {}
|
||||
_onClose() {}
|
||||
_onPosition() {}
|
||||
};
|
||||
|
||||
export class ScryingPoolSettings extends _AppBase {
|
||||
static DEFAULT_OPTIONS = {
|
||||
id: 'scrying-pool-settings',
|
||||
classes: ['scrying-pool-settings'],
|
||||
classes: ['scrying-pool', 'scrying-pool-settings'],
|
||||
window: { title: 'Scrying Pool Settings', resizable: true },
|
||||
position: { width: 400, height: 200 },
|
||||
position: { width: 320, height: 200 },
|
||||
};
|
||||
|
||||
static PARTS = {
|
||||
form: {
|
||||
content: {
|
||||
template: 'modules/scrying-pool/templates/settings.hbs',
|
||||
},
|
||||
};
|
||||
@@ -37,6 +62,12 @@ export class ScryingPoolSettings extends foundry.applications.api.ApplicationV2
|
||||
async _onRender(context, options) {
|
||||
super._onRender(context, options);
|
||||
|
||||
// Add click handler for window close button
|
||||
const windowCloseBtn = this.element.querySelector('[data-action="close"]');
|
||||
if (windowCloseBtn) {
|
||||
windowCloseBtn.addEventListener('click', () => this.close());
|
||||
}
|
||||
|
||||
// Add click handler for reopen button
|
||||
const reopenBtn = this.element.querySelector('[data-action="reopen-strip"]');
|
||||
if (reopenBtn) {
|
||||
@@ -46,7 +77,7 @@ export class ScryingPoolSettings extends foundry.applications.api.ApplicationV2
|
||||
});
|
||||
}
|
||||
|
||||
// Add click handler for close button
|
||||
// Add click handler for close strip button
|
||||
const closeBtn = this.element.querySelector('[data-action="close-strip"]');
|
||||
if (closeBtn) {
|
||||
closeBtn.addEventListener('click', () => {
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.55), 0 2px 8px rgba(0, 0, 0, 0.35),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
|
||||
/* Ensure Director's Board appears above the video strip */
|
||||
z-index: 100;
|
||||
|
||||
// ── Hide Foundry's default window header ──────────────────────────────────
|
||||
header.window-header { display: none; }
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
.scrying-pool-strip {
|
||||
background: var(--sp-bg, hsl(220, 15%, 12%));
|
||||
border-radius: 8px;
|
||||
|
||||
/* Ensure strip appears below Director's Board (z-index: 100) */
|
||||
z-index: 50;
|
||||
|
||||
// Hide Foundry's default window header — replaced by a lightweight in-content button.
|
||||
header.window-header { display: none; }
|
||||
|
||||
+194
-37
@@ -1,53 +1,210 @@
|
||||
<form class="scrying-pool-settings-form">
|
||||
<div class="form-group">
|
||||
<h2>{{localize "SCRYING_POOL.Settings.Title"}}</h2>
|
||||
<p class="hint">{{localize "SCRYING_POOL.Settings.Hint"}}</p>
|
||||
</div>
|
||||
<form class="scrying-pool scrying-pool-settings">
|
||||
<div class="scrying-pool-settings__inner">
|
||||
<!-- Drag grip -->
|
||||
<div class="scrying-pool-settings__grip" title="Drag to move">
|
||||
<i class="fa-solid fa-grip-lines"></i>
|
||||
</div>
|
||||
|
||||
<!-- Close button -->
|
||||
<button type="button" class="scrying-pool-settings__close-btn" data-action="close">
|
||||
<i class="fa-solid fa-times"></i>
|
||||
</button>
|
||||
|
||||
<!-- Header -->
|
||||
<div class="scrying-pool-settings__header">
|
||||
<h2 class="scrying-pool-settings__title">{{localize "SCRYING_POOL.Settings.Title"}}</h2>
|
||||
<p class="scrying-pool-settings__hint">{{localize "SCRYING_POOL.Settings.Hint"}}</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>{{localize "SCRYING_POOL.Settings.StripStatus"}}</label>
|
||||
{{#if hasStrip}}
|
||||
<button type="button" data-action="close-strip" class="scrying-pool-btn scrying-pool-btn-close">
|
||||
<i class="fa-solid fa-eye-slash"></i> {{localize "SCRYING_POOL.Settings.CloseStrip"}}
|
||||
</button>
|
||||
{{else}}
|
||||
<button type="button" data-action="reopen-strip" class="scrying-pool-btn scrying-pool-btn-reopen">
|
||||
<i class="fa-solid fa-eye"></i> {{localize "SCRYING_POOL.Settings.ReopenStrip"}}
|
||||
</button>
|
||||
{{/if}}
|
||||
<!-- Content -->
|
||||
<div class="scrying-pool-settings__content">
|
||||
<div class="scrying-pool-settings__status">
|
||||
<label class="scrying-pool-settings__label">{{localize "SCRYING_POOL.Settings.StripStatus"}}</label>
|
||||
{{#if hasStrip}}
|
||||
<button type="button" data-action="close-strip" class="scrying-pool-settings__btn scrying-pool-settings__btn--close">
|
||||
<i class="fa-solid fa-eye-slash"></i> {{localize "SCRYING_POOL.Settings.CloseStrip"}}
|
||||
</button>
|
||||
{{else}}
|
||||
<button type="button" data-action="reopen-strip" class="scrying-pool-settings__btn scrying-pool-settings__btn--reopen">
|
||||
<i class="fa-solid fa-eye"></i> {{localize "SCRYING_POOL.Settings.ReopenStrip"}}
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.scrying-pool-settings-form {
|
||||
padding: 15px;
|
||||
/* Match Director's Board styling */
|
||||
.scrying-pool-settings {
|
||||
background: linear-gradient(175deg, hsl(220, 18%, 13%) 0%, hsl(220, 15%, 10%) 100%);
|
||||
color: var(--sp-text-primary, #dde2e8);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
border-top: 2px solid hsl(200, 55%, 40%);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.55), 0 2px 8px rgba(0, 0, 0, 0.35),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.04);
|
||||
min-width: 320px;
|
||||
}
|
||||
.scrying-pool-settings-form .form-group {
|
||||
margin-bottom: 15px;
|
||||
|
||||
/* Hide Foundry's default window header */
|
||||
.scrying-pool-settings header.window-header {
|
||||
display: none;
|
||||
}
|
||||
.scrying-pool-settings-form .hint {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.9em;
|
||||
|
||||
.scrying-pool-settings__inner {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
.scrying-pool-btn {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
|
||||
/* Drag grip */
|
||||
.scrying-pool-settings__grip {
|
||||
width: 100%;
|
||||
height: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: grab;
|
||||
color: var(--sp-text-muted, hsl(0, 0%, 70%));
|
||||
opacity: 0.35;
|
||||
font-size: 10px;
|
||||
flex-shrink: 0;
|
||||
transition: opacity 0.15s, background 0.15s;
|
||||
user-select: none;
|
||||
border-radius: 8px 8px 0 0;
|
||||
}
|
||||
|
||||
.scrying-pool-settings__grip:hover {
|
||||
opacity: 0.8;
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
.scrying-pool-settings__grip:active {
|
||||
cursor: grabbing;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Close button */
|
||||
.scrying-pool-settings__close-btn {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 6px;
|
||||
z-index: 10;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 0;
|
||||
line-height: 20px;
|
||||
font-size: 14px;
|
||||
display: inline-flex;
|
||||
font-weight: 400;
|
||||
background: transparent;
|
||||
color: var(--sp-text-muted, hsl(0, 0%, 70%));
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
opacity: 0.5;
|
||||
transition: opacity 0.15s, background 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
.scrying-pool-settings__close-btn:hover {
|
||||
opacity: 1;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: var(--sp-text-primary, #dde2e8);
|
||||
}
|
||||
|
||||
.scrying-pool-settings__close-btn:active {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.scrying-pool-settings__header {
|
||||
padding: 12px 16px 8px;
|
||||
}
|
||||
|
||||
.scrying-pool-settings__title {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--sp-text-primary, #dde2e8);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.scrying-pool-settings__hint {
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: var(--sp-text-muted, hsl(0, 0%, 60%));
|
||||
text-align: center;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
.scrying-pool-settings__content {
|
||||
padding: 8px 16px 16px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.scrying-pool-settings__status {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.scrying-pool-btn-reopen {
|
||||
background-color: var(--success);
|
||||
color: white;
|
||||
|
||||
.scrying-pool-settings__label {
|
||||
font-size: 12px;
|
||||
color: var(--sp-text-muted, hsl(0, 0%, 65%));
|
||||
font-weight: 500;
|
||||
}
|
||||
.scrying-pool-btn-close {
|
||||
background-color: var(--danger);
|
||||
color: white;
|
||||
|
||||
/* Buttons */
|
||||
.scrying-pool-settings__btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, opacity 0.15s, transform 0.1s;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
|
||||
|
||||
i {
|
||||
font-size: 12px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
.scrying-pool-btn:hover {
|
||||
opacity: 0.9;
|
||||
|
||||
.scrying-pool-settings__btn--reopen {
|
||||
background: linear-gradient(175deg, hsl(120, 60%, 30%) 0%, hsl(120, 60%, 25%) 100%);
|
||||
color: #d0f0d0;
|
||||
border: 1px solid rgba(0, 255, 0, 0.3);
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(175deg, hsl(120, 60%, 35%) 0%, hsl(120, 60%, 30%) 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.scrying-pool-settings__btn--close {
|
||||
background: linear-gradient(175deg, hsl(0, 60%, 30%) 0%, hsl(0, 60%, 25%) 100%);
|
||||
color: #f0d0d0;
|
||||
border: 1px solid rgba(255, 0, 0, 0.3);
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(175deg, hsl(0, 60%, 35%) 0%, hsl(0, 60%, 30%) 100%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</form>
|
||||
|
||||
@@ -79,6 +79,8 @@ describe('DirectorsBoard', () => {
|
||||
expect(DirectorsBoard.DEFAULT_OPTIONS.position).toEqual({
|
||||
width: 420,
|
||||
height: 480,
|
||||
left: 20,
|
||||
top: 100,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user