Compare commits
6 Commits
816b7951fb
..
14.0.4
| Author | SHA1 | Date | |
|---|---|---|---|
| a9dbb9306a | |||
| 602b1fc8e7 | |||
| daed472b46 | |||
| d9eda8c725 | |||
| 156f786448 | |||
| 9e80c2c028 |
@@ -0,0 +1,76 @@
|
|||||||
|
name: Release Creation
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo "💡 The ${{ gitea.repository }} repository will be cloned to the runner."
|
||||||
|
|
||||||
|
- uses: RouxAntoine/checkout@v3.5.4
|
||||||
|
|
||||||
|
# get part of the tag after the `v`
|
||||||
|
- name: Extract tag version number
|
||||||
|
id: get_version
|
||||||
|
uses: battila7/get-version-action@v2
|
||||||
|
|
||||||
|
# Substitute the Manifest and Download URLs in the module.json
|
||||||
|
- name: Substitute Manifest and Download Links For Versioned Ones
|
||||||
|
id: sub_manifest_link_version
|
||||||
|
uses: microsoft/variable-substitution@v1
|
||||||
|
with:
|
||||||
|
files: 'module.json'
|
||||||
|
env:
|
||||||
|
version: ${{steps.get_version.outputs.version-without-v}}
|
||||||
|
url: https://www.uberwald.me/gitea/${{gitea.repository}}
|
||||||
|
manifest: https://www.uberwald.me/gitea/public/${{gitea.repository}}/releases/download/latest/module.json
|
||||||
|
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{gitea.event.release.tag_name}}/module.zip
|
||||||
|
|
||||||
|
# Set up Node.js
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: https://github.com/actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
# Create a zip file with all files required by the module to add to the release
|
||||||
|
- run: |
|
||||||
|
apt update -y
|
||||||
|
apt install -y zip
|
||||||
|
|
||||||
|
- run: node scripts/package.mjs
|
||||||
|
|
||||||
|
- name: Upload release assets
|
||||||
|
run: |
|
||||||
|
# Upload module.zip
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
-H "Authorization: token ${{secrets.ALLOW_PUSH_RELEASE}}" \
|
||||||
|
--data-binary @./module.zip \
|
||||||
|
"https://www.uberwald.me/api/v1/repos/${{gitea.repository}}/releases/${{gitea.event.release.id}}/assets?name=module.zip"
|
||||||
|
|
||||||
|
# Upload module.json
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: token ${{secrets.ALLOW_PUSH_RELEASE}}" \
|
||||||
|
--data-binary @./module.json \
|
||||||
|
"https://www.uberwald.me/api/v1/repos/${{gitea.repository}}/releases/${{gitea.event.release.id}}/assets?name=module.json"
|
||||||
|
|
||||||
|
- name: Publish to Foundry server
|
||||||
|
uses: https://github.com/djlechuck/foundryvtt-publish-package-action@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.FOUNDRYVTT_RELEASE_TOKEN }}
|
||||||
|
id: 'scrying-pool'
|
||||||
|
version: ${{gitea.event.release.tag_name}}
|
||||||
|
manifest: 'https://www.uberwald.me/gitea/public/${{gitea.repository}}/releases/download/latest/module.json'
|
||||||
|
notes: 'https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{gitea.event.release.tag_name}}/module.zip'
|
||||||
|
compatibility-minimum: '14'
|
||||||
|
compatibility-verified: '14'
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
name: Release Creation
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo "💡 The ${{ gitea.repository }} repository will be cloned to the runner."
|
||||||
|
|
||||||
|
- uses: RouxAntoine/checkout@v3.5.4
|
||||||
|
|
||||||
|
# get part of the tag after the `v`
|
||||||
|
- name: Extract tag version number
|
||||||
|
id: get_version
|
||||||
|
uses: battila7/get-version-action@v2
|
||||||
|
|
||||||
|
# Substitute the Manifest and Download URLs in the module.json
|
||||||
|
- name: Substitute Manifest and Download Links For Versioned Ones
|
||||||
|
id: sub_manifest_link_version
|
||||||
|
uses: microsoft/variable-substitution@v1
|
||||||
|
with:
|
||||||
|
files: 'module.json'
|
||||||
|
env:
|
||||||
|
version: ${{steps.get_version.outputs.version-without-v}}
|
||||||
|
url: https://www.uberwald.me/gitea/${{gitea.repository}}
|
||||||
|
manifest: https://www.uberwald.me/gitea/public/${{gitea.repository}}/releases/download/latest/module.json
|
||||||
|
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{gitea.event.release.tag_name}}/module.zip
|
||||||
|
|
||||||
|
# Set up Node.js
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: https://github.com/actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
# Create a zip file with all files required by the module to add to the release
|
||||||
|
- run: |
|
||||||
|
apt update -y
|
||||||
|
apt install -y zip
|
||||||
|
|
||||||
|
- run: node scripts/package.mjs
|
||||||
|
|
||||||
|
- name: Upload release assets
|
||||||
|
run: |
|
||||||
|
# Upload module.zip
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
-H "Authorization: token ${{secrets.RELEASE_SCRYING_POOL}}" \
|
||||||
|
--data-binary @./module.zip \
|
||||||
|
"https://www.uberwald.me/api/v1/repos/${{gitea.repository}}/releases/${{gitea.event.release.id}}/assets?name=module.zip"
|
||||||
|
|
||||||
|
# Upload module.json
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: token ${{secrets.ALLOW_PUSH_RELEASE}}" \
|
||||||
|
--data-binary @./module.json \
|
||||||
|
"https://www.uberwald.me/api/v1/repos/${{gitea.repository}}/releases/${{gitea.event.release.id}}/assets?name=module.json"
|
||||||
|
|
||||||
|
- name: Publish to Foundry server
|
||||||
|
uses: https://github.com/djlechuck/foundryvtt-publish-package-action@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.FOUNDRYVTT_RELEASE_TOKEN }}
|
||||||
|
id: 'scrying-pool'
|
||||||
|
version: ${{gitea.event.release.tag_name}}
|
||||||
|
manifest: 'https://www.uberwald.me/gitea/public/${{gitea.repository}}/releases/download/latest/module.json'
|
||||||
|
notes: 'https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{gitea.event.release.tag_name}}/module.zip'
|
||||||
|
compatibility-minimum: '14'
|
||||||
|
compatibility-verified: '14'
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
name: Release Creation
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo "💡 The ${{ gitea.repository }} repository will be cloned to the runner."
|
||||||
|
|
||||||
|
- uses: RouxAntoine/checkout@v3.5.4
|
||||||
|
|
||||||
|
# get part of the tag after the `v`
|
||||||
|
- name: Extract tag version number
|
||||||
|
id: get_version
|
||||||
|
uses: battila7/get-version-action@v2
|
||||||
|
|
||||||
|
# Substitute the Manifest and Download URLs in the module.json
|
||||||
|
- name: Substitute Manifest and Download Links For Versioned Ones
|
||||||
|
id: sub_manifest_link_version
|
||||||
|
uses: microsoft/variable-substitution@v1
|
||||||
|
with:
|
||||||
|
files: "module.json"
|
||||||
|
env:
|
||||||
|
version: ${{steps.get_version.outputs.version-without-v}}
|
||||||
|
url: https://www.uberwald.me/gitea/${{gitea.repository}}
|
||||||
|
manifest: https://www.uberwald.me/gitea/public/${{gitea.repository}}/releases/download/latest/module.json
|
||||||
|
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{gitea.event.release.tag_name}}/module.zip
|
||||||
|
|
||||||
|
# Set up Node.js
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: https://github.com/actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
# Create a zip file with all files required by the module to add to the release
|
||||||
|
- run: |
|
||||||
|
apt update -y
|
||||||
|
apt install -y zip
|
||||||
|
|
||||||
|
- run: node scripts/package.mjs
|
||||||
|
|
||||||
|
- name: Upload release assets
|
||||||
|
run: |
|
||||||
|
# Upload module.zip
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
-H "Authorization: token ${{secrets.RELEASE_SCRYING_POOL}}" \
|
||||||
|
--data-binary @./module.zip \
|
||||||
|
"https://www.uberwald.me/api/v1/repos/${{gitea.repository}}/releases/${{gitea.event.release.id}}/assets?name=module.zip"
|
||||||
|
|
||||||
|
# Upload module.json
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: token ${{secrets.RELEASE_SCRYING_POOL}}" \
|
||||||
|
--data-binary @./module.json \
|
||||||
|
"https://www.uberwald.me/api/v1/repos/${{gitea.repository}}/releases/${{gitea.event.release.id}}/assets?name=module.json"
|
||||||
|
|
||||||
|
- name: Publish to Foundry server
|
||||||
|
uses: https://github.com/djlechuck/foundryvtt-publish-package-action@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.FOUNDRYVTT_RELEASE_TOKEN }}
|
||||||
|
id: "scrying-pool"
|
||||||
|
version: ${{gitea.event.release.tag_name}}
|
||||||
|
manifest: "https://www.uberwald.me/gitea/public/${{gitea.repository}}/releases/download/latest/module.json"
|
||||||
|
notes: "https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{gitea.event.release.tag_name}}/module.zip"
|
||||||
|
compatibility-minimum: "14"
|
||||||
|
compatibility-verified: "14"
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
name: Release Creation
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo "💡 The ${{ gitea.repository }} repository will be cloned to the runner."
|
||||||
|
|
||||||
|
- uses: RouxAntoine/checkout@v3.5.4
|
||||||
|
|
||||||
|
# get part of the tag after the `v`
|
||||||
|
- name: Extract tag version number
|
||||||
|
id: get_version
|
||||||
|
uses: battila7/get-version-action@v2
|
||||||
|
|
||||||
|
# Substitute the Manifest and Download URLs in the module.json
|
||||||
|
- name: Substitute Manifest and Download Links For Versioned Ones
|
||||||
|
id: sub_manifest_link_version
|
||||||
|
uses: microsoft/variable-substitution@v1
|
||||||
|
with:
|
||||||
|
files: "module.json"
|
||||||
|
env:
|
||||||
|
version: ${{steps.get_version.outputs.version-without-v}}
|
||||||
|
url: https://www.uberwald.me/gitea/${{gitea.repository}}
|
||||||
|
manifest: https://www.uberwald.me/gitea/public/${{gitea.repository}}/releases/download/latest/module.json
|
||||||
|
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{gitea.event.release.tag_name}}/module.zip
|
||||||
|
|
||||||
|
# Set up Node.js
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: https://github.com/actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
# Create a zip file with all files required by the module to add to the release
|
||||||
|
- run: |
|
||||||
|
apt update -y
|
||||||
|
apt install -y zip
|
||||||
|
|
||||||
|
- run: node scripts/package.mjs
|
||||||
|
|
||||||
|
- name: Upload release assets
|
||||||
|
run: |
|
||||||
|
# Upload module.zip
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
-H "Authorization: token ${{secrets.RELEASE_SCRYING_POOL}}" \
|
||||||
|
--data-binary @./module.zip \
|
||||||
|
"https://www.uberwald.me/api/v1/repos/${{gitea.repository}}/releases/${{gitea.event.release.id}}/assets?name=module.zip"
|
||||||
|
|
||||||
|
# Upload module.json
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: token ${{secrets.RELEASE_SCRYING_POOL}}" \
|
||||||
|
--data-binary @./module.json \
|
||||||
|
"https://www.uberwald.me/api/v1/repos/${{gitea.repository}}/releases/${{gitea.event.release.id}}/assets?name=module.json"
|
||||||
|
|
||||||
|
- name: Publish to Foundry server
|
||||||
|
uses: https://github.com/djlechuck/foundryvtt-publish-package-action@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.FOUNDRYVTT_RELEASE_TOKEN }}
|
||||||
|
id: "scrying-pool"
|
||||||
|
version: ${{gitea.event.release.tag_name}}
|
||||||
|
manifest: "https://www.uberwald.me/gitea/public/${{gitea.repository}}/releases/download/latest/module.json"
|
||||||
|
notes: "https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{gitea.event.release.tag_name}}/module.zip"
|
||||||
|
compatibility-minimum: "14"
|
||||||
|
compatibility-verified: "14"
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
name: Release Creation
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- run: echo "💡 The ${{ gitea.repository }} repository will be cloned to the runner."
|
||||||
|
|
||||||
|
- uses: RouxAntoine/checkout@v3.5.4
|
||||||
|
|
||||||
|
# get part of the tag after the `v`
|
||||||
|
- name: Extract tag version number
|
||||||
|
id: get_version
|
||||||
|
uses: battila7/get-version-action@v2
|
||||||
|
|
||||||
|
# Substitute the Manifest and Download URLs in the module.json
|
||||||
|
- name: Substitute Manifest and Download Links For Versioned Ones
|
||||||
|
id: sub_manifest_link_version
|
||||||
|
uses: microsoft/variable-substitution@v1
|
||||||
|
with:
|
||||||
|
files: "module.json"
|
||||||
|
env:
|
||||||
|
version: ${{steps.get_version.outputs.version-without-v}}
|
||||||
|
url: https://www.uberwald.me/gitea/${{gitea.repository}}
|
||||||
|
manifest: https://www.uberwald.me/gitea/public/${{gitea.repository}}/releases/download/latest/module.json
|
||||||
|
download: https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{gitea.event.release.tag_name}}/module.zip
|
||||||
|
|
||||||
|
# Set up Node.js
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: https://github.com/actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
# Create a zip file with all files required by the module to add to the release
|
||||||
|
- run: |
|
||||||
|
apt update -y
|
||||||
|
apt install -y zip
|
||||||
|
|
||||||
|
- run: node scripts/package.mjs
|
||||||
|
|
||||||
|
- name: Upload release assets
|
||||||
|
run: |
|
||||||
|
# Upload module.zip
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
-H "Authorization: token ${{secrets.RELEASE_SCRYING_POOL}}" \
|
||||||
|
--data-binary @./module.zip \
|
||||||
|
"https://www.uberwald.me/api/v1/repos/${{gitea.repository}}/releases/${{gitea.event.release.id}}/assets?name=module.zip"
|
||||||
|
|
||||||
|
# Upload module.json
|
||||||
|
curl -s -X POST \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: token ${{secrets.RELEASE_SCRYING_POOL}}" \
|
||||||
|
--data-binary @./module.json \
|
||||||
|
"https://www.uberwald.me/api/v1/repos/${{gitea.repository}}/releases/${{gitea.event.release.id}}/assets?name=module.json"
|
||||||
|
|
||||||
|
# - name: Publish to Foundry server
|
||||||
|
# uses: https://github.com/djlechuck/foundryvtt-publish-package-action@v1
|
||||||
|
# with:
|
||||||
|
# token: ${{ secrets.FOUNDRYVTT_RELEASE_TOKEN }}
|
||||||
|
# id: "scrying-pool"
|
||||||
|
# version: ${{gitea.event.release.tag_name}}
|
||||||
|
# manifest: "https://www.uberwald.me/gitea/public/${{gitea.repository}}/releases/download/latest/module.json"
|
||||||
|
# notes: "https://www.uberwald.me/gitea/${{gitea.repository}}/releases/download/${{gitea.event.release.tag_name}}/module.zip"
|
||||||
|
# compatibility-minimum: "14"
|
||||||
|
# compatibility-verified: "14"
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"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.",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Morr"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibility": {
|
||||||
|
"minimum": "14",
|
||||||
|
"verified": "14"
|
||||||
|
},
|
||||||
|
"esmodules": [
|
||||||
|
"module.js"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"styles/scrying-pool.css"
|
||||||
|
],
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"lang": "en",
|
||||||
|
"name": "English",
|
||||||
|
"path": "lang/en.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"url": "${url}",
|
||||||
|
"manifest": "${manifest}",
|
||||||
|
"download": "${download}",
|
||||||
|
"flags": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"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.",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Morr"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibility": {
|
||||||
|
"minimum": "14",
|
||||||
|
"verified": "14"
|
||||||
|
},
|
||||||
|
"esmodules": [
|
||||||
|
"module.js"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"styles/scrying-pool.css"
|
||||||
|
],
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"lang": "en",
|
||||||
|
"name": "English",
|
||||||
|
"path": "lang/en.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"url": "${url}",
|
||||||
|
"manifest": "${manifest}",
|
||||||
|
"download": "${download}",
|
||||||
|
"flags": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"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.",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Morr"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibility": {
|
||||||
|
"minimum": "14",
|
||||||
|
"verified": "14"
|
||||||
|
},
|
||||||
|
"esmodules": [
|
||||||
|
"module.js"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"styles/scrying-pool.css"
|
||||||
|
],
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"lang": "en",
|
||||||
|
"name": "English",
|
||||||
|
"path": "lang/en.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packs": [
|
||||||
|
],
|
||||||
|
"url": "${url}",
|
||||||
|
"manifest": "${manifest}",
|
||||||
|
"download": "${download}",
|
||||||
|
"flags": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"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.",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Morr"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibility": {
|
||||||
|
"minimum": "14",
|
||||||
|
"verified": "14"
|
||||||
|
},
|
||||||
|
"esmodules": [
|
||||||
|
"module.js"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"styles/scrying-pool.css"
|
||||||
|
],
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"lang": "en",
|
||||||
|
"name": "English",
|
||||||
|
"path": "lang/en.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packs": [
|
||||||
|
],
|
||||||
|
"url": "${url}",
|
||||||
|
"manifest": "${manifest}",
|
||||||
|
"download": "${download}",
|
||||||
|
"flags": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"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.",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Morr"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibility": {
|
||||||
|
"minimum": "14",
|
||||||
|
"verified": "14"
|
||||||
|
},
|
||||||
|
"esmodules": [
|
||||||
|
"module.js"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"styles/scrying-pool.css"
|
||||||
|
],
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"lang": "en",
|
||||||
|
"name": "English",
|
||||||
|
"path": "lang/en.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packs": [
|
||||||
|
],
|
||||||
|
"url": "${url}",
|
||||||
|
"manifest": "${manifest}",
|
||||||
|
"download": "${download}",
|
||||||
|
"flags": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"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.",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Morr"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibility": {
|
||||||
|
"minimum": "14",
|
||||||
|
"verified": "14"
|
||||||
|
},
|
||||||
|
"esmodules": [
|
||||||
|
"module.js"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"styles/scrying-pool.css"
|
||||||
|
],
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"lang": "en",
|
||||||
|
"name": "English",
|
||||||
|
"path": "lang/en.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packs": [
|
||||||
|
],
|
||||||
|
"url": "${url}",
|
||||||
|
"manifest": "${manifest}",
|
||||||
|
"download": "${download}",
|
||||||
|
"flags": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"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.",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Morr"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibility": {
|
||||||
|
"minimum": "14",
|
||||||
|
"verified": "14"
|
||||||
|
},
|
||||||
|
"esmodules": [
|
||||||
|
"module.js"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"styles/scrying-pool.css"
|
||||||
|
],
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"lang": "en",
|
||||||
|
"name": "English",
|
||||||
|
"path": "lang/en.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packs": [
|
||||||
|
],
|
||||||
|
"url": "${url}",
|
||||||
|
"manifest": "${manifest}",
|
||||||
|
"download": "${download}",
|
||||||
|
"flags": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"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.",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Morr"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibility": {
|
||||||
|
"minimum": "14",
|
||||||
|
"verified": "14"
|
||||||
|
},
|
||||||
|
"esmodules": [
|
||||||
|
"module.js"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"styles/scrying-pool.css"
|
||||||
|
],
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"lang": "en",
|
||||||
|
"name": "English",
|
||||||
|
"path": "lang/en.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packs": [
|
||||||
|
],
|
||||||
|
"url": "${url}",
|
||||||
|
"manifest": "${manifest}",
|
||||||
|
"download": "${download}",
|
||||||
|
"flags": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"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.",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Morr"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibility": {
|
||||||
|
"minimum": "14",
|
||||||
|
"verified": "14"
|
||||||
|
},
|
||||||
|
"esmodules": [
|
||||||
|
"module.js"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"styles/scrying-pool.css"
|
||||||
|
],
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"lang": "en",
|
||||||
|
"name": "English",
|
||||||
|
"path": "lang/en.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"packs": [
|
||||||
|
],
|
||||||
|
"url": "${url}",
|
||||||
|
"manifest": "${manifest}",
|
||||||
|
"download": "${download}",
|
||||||
|
"flags": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"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.",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Morr"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibility": {
|
||||||
|
"minimum": "14",
|
||||||
|
"verified": "14"
|
||||||
|
},
|
||||||
|
"esmodules": [
|
||||||
|
"module.js"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"styles/scrying-pool.css"
|
||||||
|
],
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"lang": "en",
|
||||||
|
"name": "English",
|
||||||
|
"path": "lang/en.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"lang": "es",
|
||||||
|
"name": "Spanish",
|
||||||
|
"path": "lang/es.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"url": "${url}",
|
||||||
|
"manifest": "${manifest}",
|
||||||
|
"download": "${download}",
|
||||||
|
"flags": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"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.",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Morr"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibility": {
|
||||||
|
"minimum": "14",
|
||||||
|
"verified": "14"
|
||||||
|
},
|
||||||
|
"esmodules": [
|
||||||
|
"module.js"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"styles/scrying-pool.css"
|
||||||
|
],
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"lang": "en",
|
||||||
|
"name": "English",
|
||||||
|
"path": "lang/en.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"lang": "fr",
|
||||||
|
"name": "French",
|
||||||
|
"path": "lang/fr.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"url": "${url}",
|
||||||
|
"manifest": "${manifest}",
|
||||||
|
"download": "${download}",
|
||||||
|
"flags": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"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.",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Morr"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"compatibility": {
|
||||||
|
"minimum": "14",
|
||||||
|
"verified": "14"
|
||||||
|
},
|
||||||
|
"esmodules": [
|
||||||
|
"module.js"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"styles/scrying-pool.css"
|
||||||
|
],
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"lang": "en",
|
||||||
|
"name": "English",
|
||||||
|
"path": "lang/en.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"lang": "fr",
|
||||||
|
"name": "French",
|
||||||
|
"path": "lang/fr.json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"url": "${url}",
|
||||||
|
"manifest": "${manifest}",
|
||||||
|
"download": "${download}",
|
||||||
|
"flags": {}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# Scrying Pool Documentation Pack
|
||||||
|
|
||||||
|
✅ **Compendium is now built and ready!**
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
- `scrying-pool-docs.db` - **Pre-built SQLite compendium database** (20KB)
|
||||||
|
- `scrying-pool-guide.json` - Source JournalEntry content (English & French)
|
||||||
|
- `assets/` - Screenshot images (5 JPG files)
|
||||||
|
|
||||||
|
## What's Included
|
||||||
|
|
||||||
|
The `scrying-pool-docs.db` file contains a **JournalEntry** with:
|
||||||
|
- **2 pages**: English and French documentation
|
||||||
|
- **Professional styling** with CSS
|
||||||
|
- **5 screenshot references** (screenshot-main.jpg, screenshot-directors-board.jpg, etc.)
|
||||||
|
|
||||||
|
## Current Setup
|
||||||
|
|
||||||
|
The `module.json` defines the pack:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"packs": [
|
||||||
|
{
|
||||||
|
"name": "scrying-pool-documentation",
|
||||||
|
"label": "Scrying Pool Documentation",
|
||||||
|
"path": "packs/scrying-pool-docs.db",
|
||||||
|
"system": "",
|
||||||
|
"type": "JournalEntry"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When the module is installed in FoundryVTT, the **"Scrying Pool Documentation"** compendium will appear under the **Modules** tab with the JournalEntry already populated.
|
||||||
|
|
||||||
|
## Updating the Documentation
|
||||||
|
|
||||||
|
To update the documentation content:
|
||||||
|
|
||||||
|
### Option 1: Using the build script (Recommended)
|
||||||
|
```bash
|
||||||
|
# Edit the JSON source
|
||||||
|
nano packs/scrying-pool-guide.json
|
||||||
|
|
||||||
|
# Rebuild the database
|
||||||
|
node scripts/build-compendium.cjs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2: Manual SQLite editing
|
||||||
|
```bash
|
||||||
|
sqlite3 packs/scrying-pool-docs.db
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 3: Using fvtt-cli (Alternative)
|
||||||
|
```bash
|
||||||
|
npm install -g @foundryvtt/fvtt-cli
|
||||||
|
fvtt-cli pack create \
|
||||||
|
--input packs/scrying-pool-guide.json \
|
||||||
|
--output packs/scrying-pool-docs.db \
|
||||||
|
--type JournalEntry
|
||||||
|
```
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
packs/
|
||||||
|
├── scrying-pool-docs.db # SQLite database (compendium)
|
||||||
|
├── scrying-pool-guide.json # Source JSON (for editing)
|
||||||
|
├── README.md # This file
|
||||||
|
└── assets/
|
||||||
|
├── README.md
|
||||||
|
├── screenshot-main.jpg
|
||||||
|
├── screenshot-directors-board.jpg
|
||||||
|
├── screenshot-player-view.jpg
|
||||||
|
├── screenshot-presets.jpg
|
||||||
|
└── screenshot-badge-states.jpg
|
||||||
|
```
|
||||||
|
|
||||||
|
## Technical Details
|
||||||
|
|
||||||
|
- **Database**: SQLite3 with WAL mode enabled
|
||||||
|
- **Table**: `JournalEntry` with FoundryVTT v14 schema
|
||||||
|
- **JournalEntry ID**: `scrying-pool-guide`
|
||||||
|
- **Name**: "Scrying Pool - User Guide / Guide de l'utilisateur"
|
||||||
|
- **Pages**: 2 (English and French)
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,439 @@
|
|||||||
|
Attribution-NonCommercial-ShareAlike 4.0 International
|
||||||
|
|
||||||
|
=======================================================================
|
||||||
|
|
||||||
|
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
||||||
|
does not provide legal services or legal advice. Distribution of
|
||||||
|
Creative Commons public licenses does not create a lawyer-client or
|
||||||
|
other relationship. Creative Commons makes its licenses and related
|
||||||
|
information available on an "as-is" basis. Creative Commons gives no
|
||||||
|
warranties regarding its licenses, any material licensed under their
|
||||||
|
terms and conditions, or any related information. Creative Commons
|
||||||
|
disclaims all liability for damages resulting from their use to the
|
||||||
|
fullest extent possible.
|
||||||
|
|
||||||
|
Using Creative Commons Public Licenses
|
||||||
|
|
||||||
|
Creative Commons public licenses provide a standard set of terms and
|
||||||
|
conditions that creators and other rights holders may use to share
|
||||||
|
original works of authorship and other material subject to copyright
|
||||||
|
and certain other rights specified in the public license below. The
|
||||||
|
following considerations are for informational purposes only, are not
|
||||||
|
exhaustive, and do not form part of our licenses.
|
||||||
|
|
||||||
|
Considerations for licensors: Our public licenses are
|
||||||
|
intended for use by those authorized to give the public
|
||||||
|
permission to use material in ways otherwise restricted by
|
||||||
|
copyright and certain other rights. Our licenses are
|
||||||
|
irrevocable. Licensors should read and understand the terms
|
||||||
|
and conditions of the license they choose before applying it.
|
||||||
|
Licensors should also secure all rights necessary before
|
||||||
|
applying our licenses so that the public can reuse the
|
||||||
|
material as expected. Licensors should clearly mark any
|
||||||
|
material not subject to the license. This includes other CC-
|
||||||
|
licensed material, or material used under an exception or
|
||||||
|
limitation to copyright. More considerations for licensors:
|
||||||
|
wiki.creativecommons.org/Considerations_for_licensors
|
||||||
|
|
||||||
|
Considerations for the public: By using one of our public
|
||||||
|
licenses, a licensor grants the public permission to use the
|
||||||
|
licensed material under specified terms and conditions. If
|
||||||
|
the licensor's permission is not necessary for any reason--for
|
||||||
|
example, because of any applicable exception or limitation to
|
||||||
|
copyright--then that use is not regulated by the license. Our
|
||||||
|
licenses grant only permissions under copyright and certain
|
||||||
|
other rights that a licensor has authority to grant. Use of
|
||||||
|
the licensed material may still be restricted for other
|
||||||
|
reasons, including because others have copyright or other
|
||||||
|
rights in the material. A licensor may make special requests,
|
||||||
|
such as asking that all changes be marked or described.
|
||||||
|
Although not required by our licenses, you are encouraged to
|
||||||
|
refer to those standards and best practices.
|
||||||
|
More considerations for the public:
|
||||||
|
wiki.creativecommons.org/Considerations_for_licensees
|
||||||
|
|
||||||
|
=======================================================================
|
||||||
|
|
||||||
|
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
|
||||||
|
Public License
|
||||||
|
|
||||||
|
By exercising the Licensed Rights (defined below), You accept and agree
|
||||||
|
to be bound by the terms and conditions of this Creative Commons
|
||||||
|
Attribution-NonCommercial-ShareAlike 4.0 International Public License
|
||||||
|
("Public License"). To the extent this Public License may be
|
||||||
|
interpreted as a contract, You are granted the Licensed Rights in
|
||||||
|
consideration of Your acceptance of these terms and conditions, and the
|
||||||
|
Licensor grants You such rights in consideration of benefits the
|
||||||
|
Licensor receives from making the Licensed Material available under
|
||||||
|
these terms and conditions.
|
||||||
|
|
||||||
|
|
||||||
|
Section 1 -- Definitions.
|
||||||
|
|
||||||
|
a. Adapted Material means material subject to Copyright and Similar
|
||||||
|
Rights that is derived from or based upon the Licensed Material
|
||||||
|
and in which the Licensed Material is translated, altered,
|
||||||
|
arranged, transformed, or otherwise modified in a manner requiring
|
||||||
|
permission under the Copyright and Similar Rights held by the
|
||||||
|
Licensor. For purposes of this Public License, where the Licensed
|
||||||
|
Material is a musical work, performance, or sound recording,
|
||||||
|
Adapted Material is always produced where the Licensed Material is
|
||||||
|
synched in timed relation with a moving image.
|
||||||
|
|
||||||
|
b. Adapter's License means the license You apply to Your Copyright
|
||||||
|
and Similar Rights in Your contributions to Adapted Material in
|
||||||
|
accordance with the terms and conditions of this Public License.
|
||||||
|
|
||||||
|
c. BY-NC-SA Compatible License means a license listed at
|
||||||
|
creativecommons.org/compatiblelicenses, approved by Creative
|
||||||
|
Commons as essentially the equivalent of this Public License.
|
||||||
|
|
||||||
|
d. Copyright and Similar Rights means copyright and/or similar rights
|
||||||
|
closely related to copyright including, without limitation,
|
||||||
|
performance, broadcast, sound recording, and Sui Generis Database
|
||||||
|
Rights, without regard to how the rights are labeled or
|
||||||
|
categorized. For purposes of this Public License, the rights
|
||||||
|
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
||||||
|
Rights.
|
||||||
|
|
||||||
|
e. Effective Technological Measures means those measures that, in the
|
||||||
|
absence of proper authority, may not be circumvented under laws
|
||||||
|
fulfilling obligations under Article 11 of the WIPO Copyright
|
||||||
|
Treaty adopted on December 20, 1996, and/or similar international
|
||||||
|
agreements.
|
||||||
|
|
||||||
|
f. Exceptions and Limitations means fair use, fair dealing, and/or
|
||||||
|
any other exception or limitation to Copyright and Similar Rights
|
||||||
|
that applies to Your use of the Licensed Material.
|
||||||
|
|
||||||
|
g. License Elements means the license attributes listed in the name
|
||||||
|
of a Creative Commons Public License. The License Elements of this
|
||||||
|
Public License are Attribution, NonCommercial, and ShareAlike.
|
||||||
|
|
||||||
|
h. Licensed Material means the artistic or literary work, database,
|
||||||
|
or other material to which the Licensor applied this Public
|
||||||
|
License.
|
||||||
|
|
||||||
|
i. Licensed Rights means the rights granted to You subject to the
|
||||||
|
terms and conditions of this Public License, which are limited to
|
||||||
|
all Copyright and Similar Rights that apply to Your use of the
|
||||||
|
Licensed Material and that the Licensor has authority to license.
|
||||||
|
|
||||||
|
j. Licensor means the individual(s) or entity(ies) granting rights
|
||||||
|
under this Public License.
|
||||||
|
|
||||||
|
k. NonCommercial means not primarily intended for or directed towards
|
||||||
|
commercial advantage or monetary compensation. For purposes of
|
||||||
|
this Public License, the exchange of the Licensed Material for
|
||||||
|
other material subject to Copyright and Similar Rights by digital
|
||||||
|
file-sharing or similar means is NonCommercial provided there is
|
||||||
|
no payment of monetary compensation in connection with the
|
||||||
|
exchange.
|
||||||
|
|
||||||
|
l. Share means to provide material to the public by any means or
|
||||||
|
process that requires permission under the Licensed Rights, such
|
||||||
|
as reproduction, public display, public performance, distribution,
|
||||||
|
dissemination, communication, or importation, and to make material
|
||||||
|
available to the public including in ways that members of the
|
||||||
|
public may access the material from a place and at a time
|
||||||
|
individually chosen by them.
|
||||||
|
|
||||||
|
m. Sui Generis Database Rights means rights other than copyright
|
||||||
|
resulting from Directive 96/9/EC of the European Parliament and of
|
||||||
|
the Council of 11 March 1996 on the legal protection of databases,
|
||||||
|
as amended and/or succeeded, as well as other essentially
|
||||||
|
equivalent rights anywhere in the world.
|
||||||
|
|
||||||
|
n. You means the individual or entity exercising the Licensed Rights
|
||||||
|
under this Public License. Your has a corresponding meaning.
|
||||||
|
|
||||||
|
|
||||||
|
Section 2 -- Scope.
|
||||||
|
|
||||||
|
a. License grant.
|
||||||
|
|
||||||
|
1. Subject to the terms and conditions of this Public License,
|
||||||
|
the Licensor hereby grants You a worldwide, royalty-free,
|
||||||
|
non-sublicensable, non-exclusive, irrevocable license to
|
||||||
|
exercise the Licensed Rights in the Licensed Material to:
|
||||||
|
|
||||||
|
a. reproduce and Share the Licensed Material, in whole or
|
||||||
|
in part, for NonCommercial purposes only; and
|
||||||
|
|
||||||
|
b. produce, reproduce, and Share Adapted Material for
|
||||||
|
NonCommercial purposes only.
|
||||||
|
|
||||||
|
2. Exceptions and Limitations. For the avoidance of doubt, where
|
||||||
|
Exceptions and Limitations apply to Your use, this Public
|
||||||
|
License does not apply, and You do not need to comply with
|
||||||
|
its terms and conditions.
|
||||||
|
|
||||||
|
3. Term. The term of this Public License is specified in Section
|
||||||
|
6(a).
|
||||||
|
|
||||||
|
4. Media and formats; technical modifications allowed. The
|
||||||
|
Licensor authorizes You to exercise the Licensed Rights in
|
||||||
|
all media and formats whether now known or hereafter created,
|
||||||
|
and to make technical modifications necessary to do so. The
|
||||||
|
Licensor waives and/or agrees not to assert any right or
|
||||||
|
authority to forbid You from making technical modifications
|
||||||
|
necessary to exercise the Licensed Rights, including
|
||||||
|
technical modifications necessary to circumvent Effective
|
||||||
|
Technological Measures. For purposes of this Public License,
|
||||||
|
simply making modifications authorized by this Section 2(a)
|
||||||
|
(4) never produces Adapted Material.
|
||||||
|
|
||||||
|
5. Downstream recipients.
|
||||||
|
|
||||||
|
a. Offer from the Licensor -- Licensed Material. Every
|
||||||
|
recipient of the Licensed Material automatically
|
||||||
|
receives an offer from the Licensor to exercise the
|
||||||
|
Licensed Rights under the terms and conditions of this
|
||||||
|
Public License.
|
||||||
|
|
||||||
|
b. Additional offer from the Licensor -- Adapted Material.
|
||||||
|
Every recipient of Adapted Material from You
|
||||||
|
automatically receives an offer from the Licensor to
|
||||||
|
exercise the Licensed Rights in the Adapted Material
|
||||||
|
under the conditions of the Adapter's License You apply.
|
||||||
|
|
||||||
|
c. No downstream restrictions. You may not offer or impose
|
||||||
|
any additional or different terms or conditions on, or
|
||||||
|
apply any Effective Technological Measures to, the
|
||||||
|
Licensed Material if doing so restricts exercise of the
|
||||||
|
Licensed Rights by any recipient.
|
||||||
|
|
||||||
|
6. No endorsement. Nothing in this Public License constitutes or
|
||||||
|
may be construed as permission to assert or imply that You
|
||||||
|
are, or that Your use of the Licensed Material is, connected
|
||||||
|
with, or sponsored, endorsed, or granted official status by,
|
||||||
|
the Licensor or others designated to receive attribution as
|
||||||
|
provided in Section 3(a)(1)(A)(i).
|
||||||
|
|
||||||
|
b. Other rights.
|
||||||
|
|
||||||
|
1. Moral rights, such as the right of integrity, are not
|
||||||
|
licensed under this Public License, nor are publicity,
|
||||||
|
privacy, and/or other similar personality rights; however, to
|
||||||
|
the extent possible, the Licensor waives and/or agrees not to
|
||||||
|
assert any such rights held by the Licensor to the limited
|
||||||
|
extent necessary to allow You to exercise the Licensed
|
||||||
|
Rights, but not otherwise.
|
||||||
|
|
||||||
|
2. Patent and trademark rights are not licensed under this
|
||||||
|
Public License.
|
||||||
|
|
||||||
|
3. To the extent possible, the Licensor waives any right to
|
||||||
|
collect royalties from You for the exercise of the Licensed
|
||||||
|
Rights, whether directly or through a collecting society
|
||||||
|
under any voluntary or waivable statutory or compulsory
|
||||||
|
licensing scheme. In all other cases the Licensor expressly
|
||||||
|
reserves any right to collect such royalties, including when
|
||||||
|
the Licensed Material is used other than for NonCommercial
|
||||||
|
purposes.
|
||||||
|
|
||||||
|
|
||||||
|
Section 3 -- License Conditions.
|
||||||
|
|
||||||
|
Your exercise of the Licensed Rights is expressly made subject to the
|
||||||
|
following conditions.
|
||||||
|
|
||||||
|
a. Attribution.
|
||||||
|
|
||||||
|
1. If You Share the Licensed Material (including in modified
|
||||||
|
form), You must:
|
||||||
|
|
||||||
|
a. retain the following if it is supplied by the Licensor
|
||||||
|
with the Licensed Material:
|
||||||
|
|
||||||
|
i. identification of the creator(s) of the Licensed
|
||||||
|
Material and any others designated to receive
|
||||||
|
attribution, in any reasonable manner requested by
|
||||||
|
the Licensor (including by pseudonym if
|
||||||
|
designated);
|
||||||
|
|
||||||
|
ii. a copyright notice;
|
||||||
|
|
||||||
|
iii. a notice that refers to this Public License;
|
||||||
|
|
||||||
|
iv. a notice that refers to the disclaimer of
|
||||||
|
warranties;
|
||||||
|
|
||||||
|
v. a URI or hyperlink to the Licensed Material to the
|
||||||
|
extent reasonably practicable;
|
||||||
|
|
||||||
|
b. indicate if You modified the Licensed Material and
|
||||||
|
retain an indication of any previous modifications; and
|
||||||
|
|
||||||
|
c. indicate the Licensed Material is licensed under this
|
||||||
|
Public License, and include the text of, or the URI or
|
||||||
|
hyperlink to, this Public License.
|
||||||
|
|
||||||
|
2. You may satisfy the conditions in Section 3(a)(1) in any
|
||||||
|
reasonable manner based on the medium, means, and context in
|
||||||
|
which You Share the Licensed Material. For example, it may be
|
||||||
|
reasonable to satisfy the conditions by providing a URI or
|
||||||
|
hyperlink to a resource that includes the required
|
||||||
|
information.
|
||||||
|
|
||||||
|
3. If requested by the Licensor, You must remove any of the
|
||||||
|
information required by Section 3(a)(1)(A) to the extent
|
||||||
|
reasonably practicable.
|
||||||
|
|
||||||
|
b. ShareAlike.
|
||||||
|
|
||||||
|
In addition to the conditions in Section 3(a), if You Share
|
||||||
|
Adapted Material You produce, the following conditions also apply.
|
||||||
|
|
||||||
|
1. The Adapter's License You apply must be a Creative Commons
|
||||||
|
license with the same License Elements, this version or
|
||||||
|
later, or a BY-NC-SA Compatible License.
|
||||||
|
|
||||||
|
2. You must include the text of, or the URI or hyperlink to, the
|
||||||
|
Adapter's License You apply. You may satisfy this condition
|
||||||
|
in any reasonable manner based on the medium, means, and
|
||||||
|
context in which You Share Adapted Material.
|
||||||
|
|
||||||
|
3. You may not offer or impose any additional or different terms
|
||||||
|
or conditions on, or apply any Effective Technological
|
||||||
|
Measures to, Adapted Material that restrict exercise of the
|
||||||
|
rights granted under the Adapter's License You apply.
|
||||||
|
|
||||||
|
|
||||||
|
Section 4 -- Sui Generis Database Rights.
|
||||||
|
|
||||||
|
Where the Licensed Rights include Sui Generis Database Rights that
|
||||||
|
apply to Your use of the Licensed Material:
|
||||||
|
|
||||||
|
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
||||||
|
to extract, reuse, reproduce, and Share all or a substantial
|
||||||
|
portion of the contents of the database for NonCommercial purposes
|
||||||
|
only;
|
||||||
|
|
||||||
|
b. if You include all or a substantial portion of the database
|
||||||
|
contents in a database in which You have Sui Generis Database
|
||||||
|
Rights, then the database in which You have Sui Generis Database
|
||||||
|
Rights (but not its individual contents) is Adapted Material,
|
||||||
|
including for purposes of Section 3(b); and
|
||||||
|
|
||||||
|
c. You must comply with the conditions in Section 3(a) if You Share
|
||||||
|
all or a substantial portion of the contents of the database.
|
||||||
|
|
||||||
|
For the avoidance of doubt, this Section 4 supplements and does not
|
||||||
|
replace Your obligations under this Public License where the Licensed
|
||||||
|
Rights include other Copyright and Similar Rights.
|
||||||
|
|
||||||
|
|
||||||
|
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
||||||
|
|
||||||
|
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
||||||
|
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
||||||
|
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
||||||
|
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
||||||
|
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
||||||
|
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
||||||
|
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
||||||
|
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
||||||
|
FULLY ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY
|
||||||
|
TO YOU.
|
||||||
|
|
||||||
|
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
||||||
|
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
||||||
|
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
||||||
|
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
||||||
|
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
||||||
|
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
||||||
|
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
||||||
|
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT FULLY ALLOWED IN
|
||||||
|
FULL OR IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
||||||
|
|
||||||
|
c. The disclaimer of warranties and limitation of liability provided
|
||||||
|
above shall be interpreted in a manner that, to the extent
|
||||||
|
possible, most closely approximates an absolute disclaimer and
|
||||||
|
waiver of all liability.
|
||||||
|
|
||||||
|
|
||||||
|
Section 6 -- Term and Termination.
|
||||||
|
|
||||||
|
a. This Public License applies for the term of the Copyright and
|
||||||
|
Similar Rights licensed here. However, if You fail to comply with
|
||||||
|
this Public License, then Your rights under this Public License
|
||||||
|
terminate automatically.
|
||||||
|
|
||||||
|
b. Where Your right to use the Licensed Material has terminated under
|
||||||
|
Section 6(a), it reinstates:
|
||||||
|
|
||||||
|
1. automatically as of the date the violation is cured, provided
|
||||||
|
it is cured within 30 days of Your discovery of the
|
||||||
|
violation; or
|
||||||
|
|
||||||
|
2. upon express reinstatement by the Licensor.
|
||||||
|
|
||||||
|
For the avoidance of doubt, this Section 6(b) does not affect any
|
||||||
|
right the Licensor may have to seek remedies for Your violations
|
||||||
|
of this Public License.
|
||||||
|
|
||||||
|
c. For the avoidance of doubt, the Licensor may also offer the
|
||||||
|
Licensed Material under separate terms or conditions or stop
|
||||||
|
distributing the Licensed Material at any time; however, doing so
|
||||||
|
will not terminate this Public License.
|
||||||
|
|
||||||
|
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
||||||
|
License.
|
||||||
|
|
||||||
|
|
||||||
|
Section 7 -- Other Terms and Conditions.
|
||||||
|
|
||||||
|
a. The Licensor shall not be bound by any additional or different
|
||||||
|
terms or conditions communicated by You unless expressly agreed.
|
||||||
|
|
||||||
|
b. Any arrangements, understandings, or agreements regarding the
|
||||||
|
Licensed Material not stated herein are separate from and
|
||||||
|
independent of the terms and conditions of this Public License.
|
||||||
|
|
||||||
|
|
||||||
|
Section 8 -- Interpretation.
|
||||||
|
|
||||||
|
a. For the avoidance of doubt, this Public License does not, and
|
||||||
|
shall not be interpreted to, reduce, limit, restrict, or impose
|
||||||
|
conditions on any use of the Licensed Material that could lawfully
|
||||||
|
be made without permission under this Public License.
|
||||||
|
|
||||||
|
b. To the extent possible, if any provision of this Public License is
|
||||||
|
deemed unenforceable, it shall be automatically reformed to the
|
||||||
|
minimum extent necessary to make it enforceable. If the provision
|
||||||
|
cannot be reformed, it shall be severed from this Public License
|
||||||
|
without affecting the enforceability of the remaining terms and
|
||||||
|
conditions.
|
||||||
|
|
||||||
|
c. No term or condition of this Public License will be waived and no
|
||||||
|
failure to comply consented to unless expressly agreed to by the
|
||||||
|
Licensor.
|
||||||
|
|
||||||
|
d. Nothing in this Public License constitutes or may be interpreted
|
||||||
|
as limitation upon, or waiver of, any privileges and immunities
|
||||||
|
that apply to the Licensor or You, including from the legal
|
||||||
|
processes of any jurisdiction or authority.
|
||||||
|
|
||||||
|
=======================================================================
|
||||||
|
|
||||||
|
Creative Commons is not a party to its public
|
||||||
|
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
||||||
|
its public licenses to material it publishes and in those instances
|
||||||
|
will be considered the "Licensor." The text of the Creative Commons
|
||||||
|
public licenses is dedicated to the public domain under the CC0 Public
|
||||||
|
Domain Dedication. Except for the limited purpose of indicating that
|
||||||
|
material is shared under a Creative Commons public license or as
|
||||||
|
otherwise permitted by the Creative Commons policies published at
|
||||||
|
creativecommons.org/policies, Creative Commons does not authorize the
|
||||||
|
use of the trademark "Creative Commons" or any other trademark or logo
|
||||||
|
of Creative Commons without its prior written consent including,
|
||||||
|
without limitation, in connection with any unauthorized modifications
|
||||||
|
to any of its public licenses or any other arrangements,
|
||||||
|
understandings, or agreements concerning use of licensed material.
|
||||||
|
|
||||||
|
For the avoidance of doubt, this paragraph does not form part of the
|
||||||
|
public licenses.
|
||||||
|
|
||||||
|
=======================================================================
|
||||||
@@ -0,0 +1,195 @@
|
|||||||
|
<p align="center">
|
||||||
|
<img src="https://raw.githubusercontent.com/morr/scrying-pool/main/.github/logo.png" alt="Scrying Pool" width="128" />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h1 align="center">Scrying Pool</h1>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<em>GM camera visibility control for FoundryVTT v14+</em>
|
||||||
|
<br />
|
||||||
|
<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/"><img src="https://img.shields.io/badge/license-CC%20BY--NC--SA%204.0-lightgrey" alt="License: CC BY-NC-SA 4.0" /></a>
|
||||||
|
<br />
|
||||||
|
<sub>Français · <a href="#english">English</a></sub>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Français
|
||||||
|
|
||||||
|
**Scrying Pool** est un module FoundryVTT qui donne au MJ un contrôle total sur la visibilité des caméras des participants. Fini les flux vidéo désordonnés — gérez qui voit quoi depuis une interface flottante et discrète.
|
||||||
|
|
||||||
|
### Fonctionnalités
|
||||||
|
|
||||||
|
| Fonction | Description |
|
||||||
|
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| **Barre du MJ** | Palette flottante listant tous les participants connectés, avec leur état caméra en temps réel |
|
||||||
|
| **Director's Board** | Fenêtre de pilotage complète : grille de participants, actions groupées, mise en avant |
|
||||||
|
| **Masquer / Afficher** | Cache ou révèle un flux à la table — par clic droit ou popover |
|
||||||
|
| **Actions groupées** | Tout afficher, tout masquer, annuler la dernière action en un clic |
|
||||||
|
| **Mise en avant (Spotlight)** | `Shift+clic` sur un participant → sa vidéo s'agrandit (×2), les autres disparaissent. `Échap` pour quitter |
|
||||||
|
| **Réorganisation** | Faites glisser les participants dans la barre pour les réorganiser. Double-clic sur la poignée pour réinitialiser |
|
||||||
|
| **Mosaïque / Grille** | 6 dispositions : vertical/horizontal/mosaïque, chacune en taille S ou L |
|
||||||
|
| **Formes des tuiles** | Rond, arrondi, hexagone, octogone — appliqué aux avatars et aux vidéos |
|
||||||
|
| **Bordures** | Largeur et couleur paramétrables pour les tuiles vidéo |
|
||||||
|
| **Préréglages** | Sauvegardez et chargez des dispositions de visibilité, avec application automatique par scène |
|
||||||
|
| **Portrait personnalisé** | Les joueurs peuvent choisir une image de remplacement quand leur caméra est indisponible |
|
||||||
|
| **Confidentialité** | Panneau de consentement pour les automatismes (caméra de réaction, etc.) |
|
||||||
|
| **Notifications** | Notifications configurables (toutes, MJ seulement, silencieux) |
|
||||||
|
| **Rétablissement position** | La position de la barre est sauvegardée automatiquement toutes les 30s et au relâché du glisser |
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
1. Dans FoundryVTT, allez dans la **Modules**
|
||||||
|
2. Recherchez **Scrying Pool**
|
||||||
|
3. Cliquez **Installer**
|
||||||
|
|
||||||
|
### Utilisation
|
||||||
|
|
||||||
|
#### Barre du MJ (strip)
|
||||||
|
|
||||||
|
La barre flottante apparaît automatiquement quand vous vous connectez en tant que MJ. Elle liste tous les participants avec leur état :
|
||||||
|
|
||||||
|
- **Clic** → popover pour masquer/afficher ce participant
|
||||||
|
- **Clic droit** → menu contextuel (masquer/afficher)
|
||||||
|
- **Shift+clic** → mode Spotlight (vidéo ×2)
|
||||||
|
- **Glisser** la poignée ⟞ pour déplacer la barre
|
||||||
|
- **Glisser** un participant pour le réorganiser
|
||||||
|
- **Double-clic** sur la poignée → réinitialiser l'ordre des participants
|
||||||
|
- **Bouton `[+]`** → basculer taille S/L
|
||||||
|
- **Bouton `[⊞]`** → ouvrir le Director's Board
|
||||||
|
|
||||||
|
La barre se fondu dans le décor — pas de chrome visible tant que vous ne la survolez pas.
|
||||||
|
|
||||||
|
#### Director's Board
|
||||||
|
|
||||||
|
Ouvrable depuis la barre (bouton `[⊞]`) ou via `Ctrl+Shift+V`.
|
||||||
|
|
||||||
|
- Grille de tous les participants avec statut et indicateur d'opération en attente
|
||||||
|
- Boutons **Tout afficher** / **Tout masquer**
|
||||||
|
- **Undo** (apparaît après une action groupée)
|
||||||
|
- **Rétablir** (après une mise en avant Spotlight)
|
||||||
|
- Sélecteur de **disposition** (vertical/horizontal/mosaïque, S/L)
|
||||||
|
- Sélecteur de **forme** des tuiles (cercle, arrondi, hexagone, octogone)
|
||||||
|
- Contrôles de **bordure** (largeur, couleur)
|
||||||
|
- Réglages de **taille des widgets** (petite 60–200px, grande 60–400px)
|
||||||
|
- Sauvegarde/chargement/export/import de **préréglages**
|
||||||
|
- **Application automatique** par scène
|
||||||
|
- Activation/désactivation A/V
|
||||||
|
- **Bouton Réinitialiser la barre** (rétablit la position par défaut)
|
||||||
|
|
||||||
|
#### Raccourcis clavier
|
||||||
|
|
||||||
|
| Raccourci | Action |
|
||||||
|
| -------------- | ------------------------------------------ |
|
||||||
|
| `Ctrl+Shift+V` | Ouvrir/Fermer le Director's Board |
|
||||||
|
| `Ctrl+Shift+S` | Tout afficher |
|
||||||
|
| `Ctrl+Shift+H` | Tout masquer |
|
||||||
|
| `Ctrl+Shift+P` | Mettre en avant le participant sélectionné |
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
Module accessible depuis **Paramètres → Gérer les modules → Scrying Pool**.
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
| --------------------------- | ---------------------------------------------------------------------- |
|
||||||
|
| Afficher le flux du MJ | Quand activé, la propre caméra du MJ est visible dans la barre |
|
||||||
|
| Verbosité des notifications | Toutes / MJ seulement / Silencieux |
|
||||||
|
| Application automatique | Activer/désactiver globalement l'application des préréglages par scène |
|
||||||
|
|
||||||
|
### Support
|
||||||
|
|
||||||
|
Pour signaler un bug ou proposer une amélioration : [ouvrir un ticket](https://github.com/morr/scrying-pool/issues).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## English
|
||||||
|
|
||||||
|
**Scrying Pool** is a FoundryVTT module that gives GMs full control over participant camera visibility. No more messy video feeds — manage who sees what from a discreet floating interface.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
| Feature | Description |
|
||||||
|
| --------------------- | ---------------------------------------------------------------------------------------- |
|
||||||
|
| **GM Strip** | Floating palette listing all connected participants with real-time camera state |
|
||||||
|
| **Director's Board** | Full command window: participant grid, bulk actions, spotlight |
|
||||||
|
| **Hide / Show** | Hide or reveal a feed to the table — via right-click or popover |
|
||||||
|
| **Bulk actions** | Show all, hide all, undo last bulk action in one click |
|
||||||
|
| **Spotlight** | `Shift+click` a participant → their video enlarges (×2), others disappear. `Esc` to exit |
|
||||||
|
| **Re-order** | Drag participants in the strip to rearrange them. Double-click the grip to reset |
|
||||||
|
| **Mosaic / Grid** | 6 layouts: vertical/horizontal/mosaic, each in S or L size |
|
||||||
|
| **Tile shapes** | Circle, rounded, hexagon, octagon — applied to avatars and video feeds |
|
||||||
|
| **Borders** | Configurable width and color for video tiles |
|
||||||
|
| **Presets** | Save and load visibility layouts, with per-scene auto-apply |
|
||||||
|
| **Custom portrait** | Players can set a fallback image when their camera is unavailable |
|
||||||
|
| **Privacy** | Consent panel for automation features (reaction cam, etc.) |
|
||||||
|
| **Notifications** | Configurable notification verbosity (all, GM only, silent) |
|
||||||
|
| **Position recovery** | Strip position auto-saved every 30s and on drag release |
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
1. In FoundryVTT, go to the **Modules**
|
||||||
|
2. Search for **Scrying Pool**
|
||||||
|
3. Click **Install**
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
#### GM Strip
|
||||||
|
|
||||||
|
The floating strip appears automatically when you log in as GM. It lists all participants with their state:
|
||||||
|
|
||||||
|
- **Click** → popover to hide/show that participant
|
||||||
|
- **Right-click** → context menu (hide/show)
|
||||||
|
- **Shift+click** → Spotlight mode (×2 video)
|
||||||
|
- **Drag** the ⟞ handle to move the strip
|
||||||
|
- **Drag** a participant to reorder them
|
||||||
|
- **Double-click** the handle → reset participant order
|
||||||
|
- **`[+]` button** → toggle S/L size
|
||||||
|
- **`[⊞]` button** → open the Director's Board
|
||||||
|
|
||||||
|
The strip is stealthy — no chrome visible until hover.
|
||||||
|
|
||||||
|
#### Director's Board
|
||||||
|
|
||||||
|
Open from the strip (`[⊞]` button) or via `Ctrl+Shift+V`.
|
||||||
|
|
||||||
|
- Grid of all participants with status and pending-operation indicator
|
||||||
|
- **Show All** / **Hide All** buttons
|
||||||
|
- **Undo** (appears after a bulk action)
|
||||||
|
- **Restore** (after a Spotlight)
|
||||||
|
- **Layout** selector (vertical/horizontal/mosaic, S/L)
|
||||||
|
- **Shape** selector (circle, rounded, hexagon, octagon)
|
||||||
|
- **Border** controls (width, color)
|
||||||
|
- **Widget size** settings (small 60–200px, large 60–400px)
|
||||||
|
- **Preset** save/load/export/import
|
||||||
|
- Per-scene **auto-apply**
|
||||||
|
- A/V toggle
|
||||||
|
- **Reset Strip** button (restores default position)
|
||||||
|
|
||||||
|
#### Keyboard Shortcuts
|
||||||
|
|
||||||
|
| Shortcut | Action |
|
||||||
|
| -------------- | ----------------------------- |
|
||||||
|
| `Ctrl+Shift+V` | Open/Close Director's Board |
|
||||||
|
| `Ctrl+Shift+S` | Show All |
|
||||||
|
| `Ctrl+Shift+H` | Hide All |
|
||||||
|
| `Ctrl+Shift+P` | Spotlight focused participant |
|
||||||
|
|
||||||
|
### Settings
|
||||||
|
|
||||||
|
Access from **Settings → Manage Modules → Scrying Pool**.
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
| ---------------------- | ------------------------------------------------------------ |
|
||||||
|
| Show GM Self Feed | When enabled, the GM's own camera feed is shown in the strip |
|
||||||
|
| Notification Verbosity | All / GM Only / Silent |
|
||||||
|
| Auto-Apply | Globally enable/disable per-scene preset auto-apply |
|
||||||
|
|
||||||
|
### Support
|
||||||
|
|
||||||
|
Report bugs or request features: [open an issue](https://github.com/morr/scrying-pool/issues).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<sub>Made for FoundryVTT v14+ · <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY-NC-SA 4.0</a></sub>
|
||||||
|
</p>
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
---
|
||||||
|
title: 'Strip: Reorder, Spotlight & Auto-Snapshots'
|
||||||
|
type: 'feature'
|
||||||
|
created: '2026-05-27'
|
||||||
|
status: 'done'
|
||||||
|
baseline_commit: '816b7951fb88353b43c66e7b9f898701ea65ad2b'
|
||||||
|
context: []
|
||||||
|
---
|
||||||
|
|
||||||
|
<frozen-after-approval reason="human-owned intent — do not modify unless human renegotiates">
|
||||||
|
|
||||||
|
## Intent
|
||||||
|
|
||||||
|
**Problem:** Strip participants have a fixed order, no way to focus a single participant, and position is only saved on close.
|
||||||
|
|
||||||
|
**Approach:** Add HTML5 drag-and-drop reordering (per-GM flag), Shift+click spotlight (in-memory, one-tile focus mode), and periodic auto-save of strip position (debounced + 30s interval with viewport validation).
|
||||||
|
|
||||||
|
## Boundaries & Constraints
|
||||||
|
|
||||||
|
**Always:**
|
||||||
|
- Re-order: persists to `game.user.setFlag('scrying-pool', 'participantOrder', string[])`, per-GM only
|
||||||
|
- Spotlight: in-memory `_focusedUserId` only, no socket/persistence
|
||||||
|
- Auto-snapshots: extends existing `stripState` flag (`left, top, width, height, savedAt`), backward-compatible
|
||||||
|
- All three features are strip-only (not Director's Board)
|
||||||
|
- French locale strings in `scrying-pool.lang.fr.json`
|
||||||
|
|
||||||
|
**Ask First:** None
|
||||||
|
|
||||||
|
**Never:**
|
||||||
|
- No external drag-and-drop libraries
|
||||||
|
- No socket broadcasts for re-order or spotlight
|
||||||
|
- No CSS animation framework changes
|
||||||
|
|
||||||
|
## I/O & Edge-Case Matrix
|
||||||
|
|
||||||
|
| Scenario | Input / State | Expected Output / Behavior | Error Handling |
|
||||||
|
|----------|--------------|---------------------------|----------------|
|
||||||
|
| Re-order: drag participant | Drag start on tile | Tile shows `opacity: 0.3` during drag | N/A |
|
||||||
|
| Re-order: drop between tiles | Drop at new position | Participant order updates, flag saved, strip re-renders | Save failure silently caught |
|
||||||
|
| Re-order: double-click grip | Double-click grip area | Order resets to connection order | N/A |
|
||||||
|
| Spotlight: Shift+click | Shift+click participant | Only that participant visible, others `display:none` in DOM, strip resized | N/A |
|
||||||
|
| Spotlight: exit via Escape | Press Escape | Full list restored, `_focusedUserId` cleared | N/A |
|
||||||
|
| Spotlight: exit via button | Click exit-focus button (replaces DB icon) | Same as Escape | N/A |
|
||||||
|
| Auto-snapshot: drag ends | mouseup after drag | Debounced save to `stripState` flag | Save failure silently caught |
|
||||||
|
| Auto-snapshot: 30s timer | Interval fires | Save current position to `stripState` | Save failure silently caught |
|
||||||
|
| Auto-snapshot: position off-screen | Saved position outside viewport | Fall back to default position | Silent fallback, no error |
|
||||||
|
|
||||||
|
</frozen-after-approval>
|
||||||
|
|
||||||
|
## Code Map
|
||||||
|
|
||||||
|
- `src/ui/gm/ScryingPoolStrip.js` — All three features: drag handlers, spotlight state, auto-save timer
|
||||||
|
- `styles/components/_roster-strip.less` — Drag feedback (`opacity: 0.3`), spotlight visual state
|
||||||
|
- `templates/directors-board.hbs` — Reset strip position button
|
||||||
|
- `src/ui/gm/DirectorsBoard.js` — Reset position handler
|
||||||
|
- `module.js` — (no changes needed, instantiates the class)
|
||||||
|
- `scrying-pool.lang.fr.json` — French locale strings
|
||||||
|
|
||||||
|
## Tasks & Acceptance
|
||||||
|
|
||||||
|
**Execution:**
|
||||||
|
- [x] `src/ui/gm/ScryingPoolStrip.js` — Add `_focusedUserId`, drag handlers, _savePosition, re-order in _prepareContext
|
||||||
|
- [x] `styles/components/_roster-strip.less` — `.sp-state-focused` gold ring, drag ghost opacity
|
||||||
|
- [x] `templates/directors-board.hbs` — Reset strip position button
|
||||||
|
- [x] `src/ui/gm/DirectorsBoard.js` — `_onResetStripPosition()` handler
|
||||||
|
|
||||||
|
**Acceptance Criteria:**
|
||||||
|
- Given a strip with 3+ participants, when GM drags a participant tile to a new position, then the participant order updates and persists across re-renders
|
||||||
|
- Given any layout, when GM Shift+clicks a participant, then other participants collapse and the focused tile shows gold state ring
|
||||||
|
- Given spotlight mode is active, when GM presses Escape, then all participants return to normal
|
||||||
|
- Given the strip is open, when GM drags the strip to a new position and releases, then the position is saved
|
||||||
|
- Given a saved off-screen position, when the strip re-renders, then it appears at default coordinates
|
||||||
|
|
||||||
|
## Suggested Review Order
|
||||||
|
|
||||||
|
**Re-order + Spotlight core logic**
|
||||||
|
|
||||||
|
- Entry point: participant filtering, DnD handlers, focus toggle, position save
|
||||||
|
[`ScryingPoolStrip.js:149`](../../src/ui/gm/ScryingPoolStrip.js#L149)
|
||||||
|
|
||||||
|
- Drag-drop splice with `fromIdx < toIdx` adjustment + null guard on element
|
||||||
|
[`ScryingPoolStrip.js:1013`](../../src/ui/gm/ScryingPoolStrip.js#L1013)
|
||||||
|
|
||||||
|
- Focus toggle toggles `_focusedUserId`, re-renders; Escape exits via document listener
|
||||||
|
[`ScryingPoolStrip.js:1076`](../../src/ui/gm/ScryingPoolStrip.js#L1076)
|
||||||
|
|
||||||
|
- Auto-save: called on grip mouseup + 30s interval; cleanup on teardown
|
||||||
|
[`ScryingPoolStrip.js:1094`](../../src/ui/gm/ScryingPoolStrip.js#L1094)
|
||||||
|
|
||||||
|
- Viewport-validated position restore with negative-value guard
|
||||||
|
[`ScryingPoolStrip.js:178`](../../src/ui/gm/ScryingPoolStrip.js#L178)
|
||||||
|
|
||||||
|
**Template changes**
|
||||||
|
|
||||||
|
- `isFocused` conditional class for gold ring on focused participant
|
||||||
|
[`roster-strip.hbs:54`](../../templates/roster-strip.hbs#L54)
|
||||||
|
|
||||||
|
- Reset strip position button in Director's Board footer
|
||||||
|
[`directors-board.hbs:140`](../../templates/directors-board.hbs#L140)
|
||||||
|
|
||||||
|
**CSS**
|
||||||
|
|
||||||
|
- Gold state ring for `.sp-state-focused`, drag ghost opacity, drop target indicator
|
||||||
|
[`_roster-strip.less:517`](../../styles/components/_roster-strip.less#L517)
|
||||||
|
|
||||||
|
**Director's Board**
|
||||||
|
|
||||||
|
- Reset position handler clears stripState flag
|
||||||
|
[`DirectorsBoard.js:861`](../../src/ui/gm/DirectorsBoard.js#L861)
|
||||||
|
|
||||||
|
**Locales**
|
||||||
|
|
||||||
|
- Reset strip button strings (EN + FR)
|
||||||
|
[`en.json:82`](../../lang/en.json#L82) · [`fr.json:82`](../../lang/fr.json#L82)
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
**Commands:**
|
||||||
|
- `npm run lint` -- expected: 0 errors
|
||||||
|
- `npm run typecheck` -- expected: 0 errors
|
||||||
|
- `npm run test` -- expected: all tests pass
|
||||||
@@ -12,7 +12,12 @@ inputDocuments:
|
|||||||
workflowType: 'architecture'
|
workflowType: 'architecture'
|
||||||
project_name: 'video-view-manager'
|
project_name: 'video-view-manager'
|
||||||
user_name: 'Morr'
|
user_name: 'Morr'
|
||||||
date: '2026-05-20'
|
date: '2026-05-27'
|
||||||
|
extendedAt: '2026-05-27'
|
||||||
|
extendedFeatures:
|
||||||
|
- reorder-participants
|
||||||
|
- spotlight-focus
|
||||||
|
- auto-position-snapshots
|
||||||
---
|
---
|
||||||
|
|
||||||
# Architecture Decision Document: Video View Manager (Scrying Pool)
|
# Architecture Decision Document: Video View Manager (Scrying Pool)
|
||||||
@@ -1091,3 +1096,82 @@ both critical gaps found during validation were resolved inline without reopenin
|
|||||||
Story 0 scaffold — `module.json` + `tsconfig.json` + `vitest.config.js` + `.eslintrc.js` +
|
Story 0 scaffold — `module.json` + `tsconfig.json` + `vitest.config.js` + `.eslintrc.js` +
|
||||||
`scripts/package.mjs` + `src/contracts/` (with validators + frozen fixtures) +
|
`scripts/package.mjs` + `src/contracts/` (with validators + frozen fixtures) +
|
||||||
`.gitea/workflows/ci.yml` — all Story 0 ACs green before any Story 1 code.
|
`.gitea/workflows/ci.yml` — all Story 0 ACs green before any Story 1 code.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature Addendum (2026-05-27): Re-order Participants
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
Strip participant order is fixed to user connection order. GM cannot rearrange participants to match table seating, spotlight priority, or personal preference.
|
||||||
|
|
||||||
|
### Decision: Drag-and-drop via native HTML5 API
|
||||||
|
|
||||||
|
| Decision | Choice | Rationale |
|
||||||
|
|---|---|---|
|
||||||
|
| Mechanism | HTML5 Drag & Drop (`draggable`, `dragstart`/`dragover`/`drop` events) | Zero dependencies; works in Foundry's embedded Chromium; no external lib needed |
|
||||||
|
| Persistence | User flag: `game.user.setFlag('scrying-pool', 'participantOrder', string[])` | Per-GM order; does not affect other GMs or players |
|
||||||
|
| Storage format | Ordered array of user IDs | Minimal, sortable, forward-compatible |
|
||||||
|
| Visual feedback | `opacity: 0.3` on dragged tile + `box-shadow` drop indicator on target gap | Lightweight; no layout shift |
|
||||||
|
| Reset | Double-click the grip area → reset to connection order | Escape hatch if order gets confusing |
|
||||||
|
| Scope | Strip only (not Director's Board) | Strip is the primary real-estate; DB reorder adds complexity with no clear need |
|
||||||
|
|
||||||
|
### Implementation Notes
|
||||||
|
- `ScryingPoolStrip` gets `_onDragStart`, `_onDragOver`, `_onDrop`, `_onDragEnd` handlers
|
||||||
|
- Attached in `_onRender` via `el.addEventListener`
|
||||||
|
- On drop: reorder participant list in `_prepareContext`, persist to user flag
|
||||||
|
- `_prepareContext` reads flag, applies order before filtering hidden participants
|
||||||
|
- No socket broadcast — order is per-GM/local-only
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature Addendum (2026-05-27): Spotlight / Focus
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
In all layouts, participants share equal visual weight. GM cannot temporarily focus on one participant's video feed (e.g., a player speaking, a dramatic reveal).
|
||||||
|
|
||||||
|
### Decision: One-tile-expand mode within the strip
|
||||||
|
|
||||||
|
| Decision | Choice | Rationale |
|
||||||
|
|---|---|---|
|
||||||
|
| Activation | Click on participant avatar while holding `Shift` (or context menu → "Focus") | Intentional action; avoids accidental triggers |
|
||||||
|
| Visual | Selected tile expands to fill strip content area; other tiles collapse to `height: 0; overflow: hidden` (preserved in DOM for instant restore) | No layout reflow on restore; preserved DOM state |
|
||||||
|
| Restore | Click `Shift+click` again, or click an "Exit focus" button that replaces the toolbar, or press `Escape` | Multiple escape hatches |
|
||||||
|
| State | In-memory only: `_focusedUserId: string|null` on `ScryingPoolStrip` | Ephemeral — no persistence needed |
|
||||||
|
| Indicator | Focused tile gets `.sp-state-focused` class → gold state ring (`--sp-urgency-director`) | Visual consistency with existing state ring system |
|
||||||
|
| Strip sizing | `setPosition` recomputed with `1` participant during focus | Window snaps to single-tile dimensions |
|
||||||
|
| Layout compatibility | Works in all layouts (vertical, horizontal, mosaic) | Tile fills available space via same CSS that handles single-participant edge case |
|
||||||
|
| Director's Board | Unaffected — spotlight is strip-only | DB maintains overview while strip focuses |
|
||||||
|
|
||||||
|
### Implementation Notes
|
||||||
|
- `_focusedUserId` field on `ScryingPoolStrip`
|
||||||
|
- `_prepareContext` filters or transforms participant list: if `_focusedUserId` set, only that participant is visible; others have `hidden: true` (but preserved in DOM)
|
||||||
|
- Restore clears `_focusedUserId` → re-render → full list visible
|
||||||
|
- CSS: `.sp-participant-avatar.sp-state-focused` inherits existing state ring pattern (green → gold via `--sp-urgency-director`)
|
||||||
|
- No socket broadcast — purely local UI state
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Feature Addendum (2026-05-27): Auto Position Snapshots
|
||||||
|
|
||||||
|
### Problem
|
||||||
|
Strip position is saved only on close. If the browser window is resized, display changed, or the strip is accidentally dragged off-screen, there is no way to restore a known-good position without relaunching.
|
||||||
|
|
||||||
|
### Decision: Periodic auto-save + explicit save/restore
|
||||||
|
|
||||||
|
| Decision | Choice | Rationale |
|
||||||
|
|---|---|---|
|
||||||
|
| Auto-save trigger | `debounced` save on `mouseup` after drag ends | Saves only when user finishes moving; no save storm during drag |
|
||||||
|
| Auto-save interval | Also every 30s via `setInterval` while strip is open | Safety net if drag event fails to fire |
|
||||||
|
| Storage | User flag: `game.user.setFlag('scrying-pool', 'stripState')` | Already partially used (saves on close); extend to include timestamp |
|
||||||
|
| Save payload | `{ left, top, width, height, savedAt }` | Position + dimensions + timestamp for diagnostics |
|
||||||
|
| Restore trigger | On `_onRender` — if saved position exists and strip has no explicit position yet | First render gets saved position |
|
||||||
|
| Reset | Director's Board button "Reset strip position" → clears flag + re-renders at default position | Manual escape hatch |
|
||||||
|
| Multi-monitor safety | Validate `saved.left` and `saved.top` are within available viewport (`window.screen.availWidth/Height`) before applying | Prevents strip from loading off-screen after monitor config change |
|
||||||
|
|
||||||
|
### Implementation Notes
|
||||||
|
- Extend existing `_loadPosition()` in `ScryingPoolStrip` — already reads `stripState` flag
|
||||||
|
- Add `_savePosition()` called on `mouseup` after drag + every 30s interval
|
||||||
|
- Viewport validation: `saved.left < screen.availWidth - 50 && saved.top < screen.availHeight - 50`
|
||||||
|
- On validation failure: silently fall back to default position (no error notification)
|
||||||
|
- Director's Board: add "Reset strip position" button (minor template change)
|
||||||
|
- Extends existing `stripState` flag — no new flags needed, backward-compatible with old saves
|
||||||
|
|||||||
+2
-1
@@ -79,7 +79,8 @@
|
|||||||
"avModeEnable": "Enable A/V",
|
"avModeEnable": "Enable A/V",
|
||||||
"avModeDisable": "Disable A/V",
|
"avModeDisable": "Disable A/V",
|
||||||
"avConfig": "A/V Settings…",
|
"avConfig": "A/V Settings…",
|
||||||
"avConfigTitle": "Open Foundry A/V server settings (signaling server, LiveKit, etc.)"
|
"avConfigTitle": "Open Foundry A/V server settings (signaling server, LiveKit, etc.)",
|
||||||
|
"resetStrip": "Reset Strip Position"
|
||||||
},
|
},
|
||||||
"bulk": {
|
"bulk": {
|
||||||
"showAll": "Show All",
|
"showAll": "Show All",
|
||||||
|
|||||||
+2
-1
@@ -79,7 +79,8 @@
|
|||||||
"avModeEnable": "Activer A/V",
|
"avModeEnable": "Activer A/V",
|
||||||
"avModeDisable": "Désactiver A/V",
|
"avModeDisable": "Désactiver A/V",
|
||||||
"avConfig": "Paramètres A/V...",
|
"avConfig": "Paramètres A/V...",
|
||||||
"avConfigTitle": "Ouvrir les paramètres du serveur A/V de Foundry (serveur de signalisation, LiveKit, etc.)"
|
"avConfigTitle": "Ouvrir les paramètres du serveur A/V de Foundry (serveur de signalisation, LiveKit, etc.)",
|
||||||
|
"resetStrip": "Réinitialiser la position de la barre"
|
||||||
},
|
},
|
||||||
"bulk": {
|
"bulk": {
|
||||||
"showAll": "Tout afficher",
|
"showAll": "Tout afficher",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"id": "scrying-pool",
|
"id": "scrying-pool",
|
||||||
"title": "Scrying Pool",
|
"title": "Scrying Pool",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
|
"license": "CC-BY-NC-SA-4.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": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "scrying-pool",
|
"name": "scrying-pool",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
|
"license": "CC-BY-NC-SA-4.0",
|
||||||
"description": "FoundryVTT v14 module — Scrying Pool camera visibility control",
|
"description": "FoundryVTT v14 module — Scrying Pool camera visibility control",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -382,13 +382,29 @@ export class DirectorsBoard extends _AppBase {
|
|||||||
// Defaults match the settings registration in module.js
|
// Defaults match the settings registration in module.js
|
||||||
const widgetWidthSm = this._adapter.settings?.get?.('widgetWidthSm') ?? '83';
|
const widgetWidthSm = this._adapter.settings?.get?.('widgetWidthSm') ?? '83';
|
||||||
const widgetWidthMd = this._adapter.settings?.get?.('widgetWidthMd') ?? '150';
|
const widgetWidthMd = this._adapter.settings?.get?.('widgetWidthMd') ?? '150';
|
||||||
const WIDTH_OPTIONS = [
|
const SM_WIDTH_OPTIONS = [
|
||||||
|
{ value: '60', label: '60px' },
|
||||||
|
{ value: '70', label: '70px' },
|
||||||
|
{ value: '80', label: '80px' },
|
||||||
|
{ value: '90', label: '90px' },
|
||||||
|
{ value: '100', label: '100px' },
|
||||||
|
{ value: '120', label: '120px' },
|
||||||
|
{ value: '140', label: '140px' },
|
||||||
|
{ value: '160', label: '160px' },
|
||||||
|
{ value: '180', label: '180px' },
|
||||||
|
{ value: '200', label: '200px' },
|
||||||
|
];
|
||||||
|
const MD_WIDTH_OPTIONS = [
|
||||||
{ value: '60', label: '60px' },
|
{ value: '60', label: '60px' },
|
||||||
{ value: '80', label: '80px' },
|
{ value: '80', label: '80px' },
|
||||||
{ value: '100', label: '100px' },
|
{ value: '100', label: '100px' },
|
||||||
{ value: '120', label: '120px' },
|
{ value: '120', label: '120px' },
|
||||||
{ value: '150', label: '150px' },
|
{ value: '150', label: '150px' },
|
||||||
{ value: '200', label: '200px' },
|
{ value: '200', label: '200px' },
|
||||||
|
{ value: '250', label: '250px' },
|
||||||
|
{ value: '300', label: '300px' },
|
||||||
|
{ value: '350', label: '350px' },
|
||||||
|
{ value: '400', label: '400px' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Tile shape selector
|
// Tile shape selector
|
||||||
@@ -433,7 +449,8 @@ export class DirectorsBoard extends _AppBase {
|
|||||||
tileBorderColor: currentTileBorderColor,
|
tileBorderColor: currentTileBorderColor,
|
||||||
tileBorderWidths: TILE_BORDER_WIDTHS,
|
tileBorderWidths: TILE_BORDER_WIDTHS,
|
||||||
// Story 5.2: Video widget width customization
|
// Story 5.2: Video widget width customization
|
||||||
widthOptions: WIDTH_OPTIONS,
|
smWidthOptions: SM_WIDTH_OPTIONS,
|
||||||
|
mdWidthOptions: MD_WIDTH_OPTIONS,
|
||||||
widgetWidthSm,
|
widgetWidthSm,
|
||||||
widgetWidthMd,
|
widgetWidthMd,
|
||||||
};
|
};
|
||||||
@@ -496,6 +513,7 @@ export class DirectorsBoard extends _AppBase {
|
|||||||
case 'set-dock-layout': this._onSetDockLayout(btn.dataset.layout); break;
|
case 'set-dock-layout': this._onSetDockLayout(btn.dataset.layout); break;
|
||||||
case 'set-tile-shape': this._onSetTileShape(btn.dataset.shape); break;
|
case 'set-tile-shape': this._onSetTileShape(btn.dataset.shape); break;
|
||||||
case 'set-tile-border-width': this._onSetTileBorderWidth(parseInt(btn.dataset.width, 10)); break;
|
case 'set-tile-border-width': this._onSetTileBorderWidth(parseInt(btn.dataset.width, 10)); break;
|
||||||
|
case 'reset-strip-position': this._onResetStripPosition(); break;
|
||||||
case 'close': this.close(); break;
|
case 'close': this.close(); break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -840,6 +858,17 @@ export class DirectorsBoard extends _AppBase {
|
|||||||
if (this.rendered) this.render({ force: true });
|
if (this.rendered) this.render({ force: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the saved strip position flag so the strip defaults to its original position.
|
||||||
|
*/
|
||||||
|
_onResetStripPosition() {
|
||||||
|
try {
|
||||||
|
game.user?.setFlag?.('scrying-pool', 'stripState', null);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[ScryingPool] Failed to reset strip position:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the PresetSaveDialog for saving the current visibility matrix as a preset.
|
* Opens the PresetSaveDialog for saving the current visibility matrix as a preset.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -149,9 +149,26 @@ export class ScryingPoolStrip extends _AppBase {
|
|||||||
this._healthCheckInterval = null;
|
this._healthCheckInterval = null;
|
||||||
/** @type {number|string|null} */
|
/** @type {number|string|null} */
|
||||||
this._userConnectedHookId = null;
|
this._userConnectedHookId = null;
|
||||||
|
/** @type {string|null} */
|
||||||
|
this._focusedUserId = null;
|
||||||
|
/** @type {number|null} */
|
||||||
|
this._positionSaveTimer = null;
|
||||||
|
|
||||||
// Load saved position from user flags
|
// Load saved position from user flags
|
||||||
this._loadPosition();
|
this._loadPosition();
|
||||||
|
|
||||||
|
/** Bound keydown handler for Escape-to-exit-focus */
|
||||||
|
this._onDocumentKeydown = (e) => {
|
||||||
|
if (e.key === 'Escape' && this._focusedUserId) {
|
||||||
|
this._focusedUserId = null;
|
||||||
|
if (typeof this.render === 'function') {
|
||||||
|
this.render({ force: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (typeof document?.addEventListener === 'function') {
|
||||||
|
document.addEventListener('keydown', this._onDocumentKeydown);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Loads saved window position from GM user flag. */
|
/** Loads saved window position from GM user flag. */
|
||||||
@@ -159,8 +176,13 @@ export class ScryingPoolStrip extends _AppBase {
|
|||||||
try {
|
try {
|
||||||
const saved = game.user?.getFlag?.('scrying-pool', 'stripState');
|
const saved = game.user?.getFlag?.('scrying-pool', 'stripState');
|
||||||
if (saved?.left != null && saved?.top != null) {
|
if (saved?.left != null && saved?.top != null) {
|
||||||
if (this.options?.position) {
|
if (saved.left < 0 || saved.top < 0) return;
|
||||||
Object.assign(this.options.position, { left: saved.left, top: saved.top });
|
const screenW = typeof window !== 'undefined' ? (window.screen?.availWidth ?? Infinity) : Infinity;
|
||||||
|
const screenH = typeof window !== 'undefined' ? (window.screen?.availHeight ?? Infinity) : Infinity;
|
||||||
|
if (saved.left < screenW - 50 && saved.top < screenH - 50) {
|
||||||
|
if (this.options?.position) {
|
||||||
|
Object.assign(this.options.position, { left: saved.left, top: saved.top });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (_e) { /* no-op in test environment */ }
|
} catch (_e) { /* no-op in test environment */ }
|
||||||
@@ -195,6 +217,22 @@ export class ScryingPoolStrip extends _AppBase {
|
|||||||
? userIds
|
? userIds
|
||||||
: userIds.filter(id => id !== currentUserId);
|
: userIds.filter(id => id !== currentUserId);
|
||||||
|
|
||||||
|
// Re-order participants per GM's saved order (drag-and-drop)
|
||||||
|
try {
|
||||||
|
const savedOrder = game.user?.getFlag?.('scrying-pool', 'participantOrder');
|
||||||
|
if (Array.isArray(savedOrder) && savedOrder.length > 0) {
|
||||||
|
const orderMap = new Map(savedOrder.map((id, idx) => [id, idx]));
|
||||||
|
filteredUserIds.sort((a, b) => {
|
||||||
|
const ai = orderMap.get(a);
|
||||||
|
const bi = orderMap.get(b);
|
||||||
|
if (ai !== undefined && bi !== undefined) return ai - bi;
|
||||||
|
if (ai !== undefined) return -1;
|
||||||
|
if (bi !== undefined) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (_e) { /* no-op in test environment */ }
|
||||||
|
|
||||||
// Check if we have stream access for video replacement (full AV replacement mode)
|
// Check if we have stream access for video replacement (full AV replacement mode)
|
||||||
const hasStreamAccess = this._adapter.webrtc?.getMediaStreamForUser !== undefined;
|
const hasStreamAccess = this._adapter.webrtc?.getMediaStreamForUser !== undefined;
|
||||||
|
|
||||||
@@ -207,8 +245,16 @@ export class ScryingPoolStrip extends _AppBase {
|
|||||||
this._portraitFallbackHandler
|
this._portraitFallbackHandler
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Mark focused participant for gold ring visual
|
||||||
|
participants.forEach(p => { p.isFocused = this._focusedUserId === p.userId; });
|
||||||
|
|
||||||
// Remove hidden participants from the strip — they are managed via the Directors Board.
|
// Remove hidden participants from the strip — they are managed via the Directors Board.
|
||||||
const visibleParticipants = participants.filter(p => p.state !== 'hidden');
|
let visibleParticipants = participants.filter(p => p.state !== 'hidden');
|
||||||
|
|
||||||
|
// Spotlight: if a participant is focused, only show that one
|
||||||
|
if (this._focusedUserId) {
|
||||||
|
visibleParticipants = visibleParticipants.filter(p => p.userId === this._focusedUserId);
|
||||||
|
}
|
||||||
|
|
||||||
// Dock layout: world setting gives direction+canonical size; client override only applies if user toggled
|
// Dock layout: world setting gives direction+canonical size; client override only applies if user toggled
|
||||||
const rawLayout = this._adapter.settings?.get?.('dockLayout');
|
const rawLayout = this._adapter.settings?.get?.('dockLayout');
|
||||||
@@ -228,6 +274,8 @@ export class ScryingPoolStrip extends _AppBase {
|
|||||||
const widgetWidthMd = this._adapter.settings?.get?.('widgetWidthMd') ?? '150';
|
const widgetWidthMd = this._adapter.settings?.get?.('widgetWidthMd') ?? '150';
|
||||||
const isLarge = effectiveSize === 'md';
|
const isLarge = effectiveSize === 'md';
|
||||||
const effectiveWidth = isLarge ? widgetWidthMd : widgetWidthSm;
|
const effectiveWidth = isLarge ? widgetWidthMd : widgetWidthSm;
|
||||||
|
const isSpotlightActive = !!this._focusedUserId;
|
||||||
|
const widgetWidth = isSpotlightActive ? String(parseInt(effectiveWidth, 10) * 2) : effectiveWidth;
|
||||||
|
|
||||||
const isGM = this._adapter.users.isGM?.() ?? false;
|
const isGM = this._adapter.users.isGM?.() ?? false;
|
||||||
|
|
||||||
@@ -248,7 +296,8 @@ export class ScryingPoolStrip extends _AppBase {
|
|||||||
hasStreamAccess,
|
hasStreamAccess,
|
||||||
isGM,
|
isGM,
|
||||||
// Story 5.2: Video widget width customization
|
// Story 5.2: Video widget width customization
|
||||||
widgetWidth: effectiveWidth,
|
isSpotlightActive,
|
||||||
|
widgetWidth,
|
||||||
// Tile shape
|
// Tile shape
|
||||||
tileShape,
|
tileShape,
|
||||||
// Tile border
|
// Tile border
|
||||||
@@ -266,6 +315,11 @@ export class ScryingPoolStrip extends _AppBase {
|
|||||||
el.querySelectorAll('[data-action="open-popover"]').forEach(btn => {
|
el.querySelectorAll('[data-action="open-popover"]').forEach(btn => {
|
||||||
const userId = btn.dataset.userId;
|
const userId = btn.dataset.userId;
|
||||||
btn.addEventListener('click', e => {
|
btn.addEventListener('click', e => {
|
||||||
|
if (e.shiftKey) {
|
||||||
|
e.stopPropagation();
|
||||||
|
this._toggleFocus(userId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
this._openPopover(userId, btn);
|
this._openPopover(userId, btn);
|
||||||
});
|
});
|
||||||
@@ -276,6 +330,33 @@ export class ScryingPoolStrip extends _AppBase {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Drag-and-drop reordering of participants
|
||||||
|
// Remove old DnD listeners first to prevent duplicates on re-render
|
||||||
|
el.querySelectorAll('.sp-strip__participant-item').forEach(item => {
|
||||||
|
const existing = item._dndHandlers;
|
||||||
|
if (existing) {
|
||||||
|
item.removeEventListener('dragstart', existing.dragstart);
|
||||||
|
item.removeEventListener('dragover', existing.dragover);
|
||||||
|
item.removeEventListener('drop', existing.drop);
|
||||||
|
item.removeEventListener('dragend', existing.dragend);
|
||||||
|
item.removeEventListener('dragleave', existing.dragleave);
|
||||||
|
}
|
||||||
|
const handlers = {
|
||||||
|
dragstart: e => this._onDragStart(e),
|
||||||
|
dragover: e => this._onDragOver(e),
|
||||||
|
drop: e => this._onDrop(e),
|
||||||
|
dragend: e => this._onDragEnd(e),
|
||||||
|
dragleave: e => this._onDragLeave(e),
|
||||||
|
};
|
||||||
|
item._dndHandlers = handlers;
|
||||||
|
item.draggable = true;
|
||||||
|
item.addEventListener('dragstart', handlers.dragstart);
|
||||||
|
item.addEventListener('dragover', handlers.dragover);
|
||||||
|
item.addEventListener('drop', handlers.drop);
|
||||||
|
item.addEventListener('dragend', handlers.dragend);
|
||||||
|
item.addEventListener('dragleave', handlers.dragleave);
|
||||||
|
});
|
||||||
|
|
||||||
const toggle = el.querySelector('[data-action="toggle-expanded"]');
|
const toggle = el.querySelector('[data-action="toggle-expanded"]');
|
||||||
if (toggle) {
|
if (toggle) {
|
||||||
toggle.addEventListener('click', () => this._toggleExpanded());
|
toggle.addEventListener('click', () => this._toggleExpanded());
|
||||||
@@ -298,8 +379,13 @@ export class ScryingPoolStrip extends _AppBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Drag grip — custom drag implementation (Foundry v14 ApplicationV1 does not expose its drag handler)
|
// Drag grip — custom drag implementation (Foundry v14 ApplicationV1 does not expose its drag handler)
|
||||||
|
// Double-click grip resets participant order to connection order
|
||||||
const grip = el.querySelector('[data-action="drag-grip"]');
|
const grip = el.querySelector('[data-action="drag-grip"]');
|
||||||
if (grip) {
|
if (grip) {
|
||||||
|
grip.addEventListener('dblclick', e => {
|
||||||
|
e.preventDefault();
|
||||||
|
this._resetParticipantOrder();
|
||||||
|
});
|
||||||
grip.addEventListener('mousedown', e => {
|
grip.addEventListener('mousedown', e => {
|
||||||
if (e.button !== 0) return;
|
if (e.button !== 0) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -316,6 +402,7 @@ export class ScryingPoolStrip extends _AppBase {
|
|||||||
const onUp = () => {
|
const onUp = () => {
|
||||||
document.removeEventListener('mousemove', onMove);
|
document.removeEventListener('mousemove', onMove);
|
||||||
document.removeEventListener('mouseup', onUp);
|
document.removeEventListener('mouseup', onUp);
|
||||||
|
this._savePosition();
|
||||||
};
|
};
|
||||||
document.addEventListener('mousemove', onMove);
|
document.addEventListener('mousemove', onMove);
|
||||||
document.addEventListener('mouseup', onUp);
|
document.addEventListener('mouseup', onUp);
|
||||||
@@ -352,12 +439,15 @@ export class ScryingPoolStrip extends _AppBase {
|
|||||||
setVar('--sp-tile-border-active', bw > 0 ? '1' : '0');
|
setVar('--sp-tile-border-active', bw > 0 ? '1' : '0');
|
||||||
|
|
||||||
// Sync the outer Application window width with the selected dock layout.
|
// Sync the outer Application window width with the selected dock layout.
|
||||||
|
// Spotlight mode doubles the strip window to fit the enlarged tile.
|
||||||
if (typeof this.setPosition === 'function') {
|
if (typeof this.setPosition === 'function') {
|
||||||
const layout = context?.dockLayout ?? 'vertical-sm';
|
const layout = context?.dockLayout ?? 'vertical-sm';
|
||||||
const n = context?.participants?.length ?? 0;
|
const n = context?.participants?.length ?? 0;
|
||||||
const width = this._computeStripWidth(layout, n);
|
const spotlightMultiplier = context?.isSpotlightActive ? 2 : 1;
|
||||||
|
const width = this._computeStripWidth(layout, n) * spotlightMultiplier;
|
||||||
const height = this._computeStripHeight(layout, n);
|
const height = this._computeStripHeight(layout, n);
|
||||||
this.setPosition(height === 'auto' ? { width, height: 'auto' } : { width, height });
|
const adjustedHeight = height === 'auto' ? 'auto' : height * spotlightMultiplier;
|
||||||
|
this.setPosition(adjustedHeight === 'auto' ? { width, height: 'auto' } : { width, height: adjustedHeight });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,6 +535,11 @@ export class ScryingPoolStrip extends _AppBase {
|
|||||||
this._activePopover = null;
|
this._activePopover = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove document-level keydown listener
|
||||||
|
if (typeof document?.removeEventListener === 'function') {
|
||||||
|
document.removeEventListener('keydown', this._onDocumentKeydown);
|
||||||
|
}
|
||||||
|
|
||||||
// Tear down stream monitoring
|
// Tear down stream monitoring
|
||||||
this._teardownStreamMonitoring();
|
this._teardownStreamMonitoring();
|
||||||
|
|
||||||
@@ -708,6 +803,9 @@ export class ScryingPoolStrip extends _AppBase {
|
|||||||
// Periodic health check every 15 seconds
|
// Periodic health check every 15 seconds
|
||||||
this._healthCheckInterval = setInterval(() => this._checkVideoStreamHealth(), 15000);
|
this._healthCheckInterval = setInterval(() => this._checkVideoStreamHealth(), 15000);
|
||||||
|
|
||||||
|
// Auto-save strip position every 30 seconds
|
||||||
|
this._positionSaveTimer = setInterval(() => this._savePosition(), 30000);
|
||||||
|
|
||||||
// Watch for user connection changes to refresh streams
|
// Watch for user connection changes to refresh streams
|
||||||
if (typeof Hooks !== 'undefined') {
|
if (typeof Hooks !== 'undefined') {
|
||||||
this._userConnectedHookId = Hooks.on('userConnected', (userId, connected) => {
|
this._userConnectedHookId = Hooks.on('userConnected', (userId, connected) => {
|
||||||
@@ -728,6 +826,12 @@ export class ScryingPoolStrip extends _AppBase {
|
|||||||
this._healthCheckInterval = null;
|
this._healthCheckInterval = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear position save timer
|
||||||
|
if (this._positionSaveTimer !== null) {
|
||||||
|
clearInterval(this._positionSaveTimer);
|
||||||
|
this._positionSaveTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
// Remove userConnected hook
|
// Remove userConnected hook
|
||||||
if (this._userConnectedHookId !== null && typeof Hooks !== 'undefined') {
|
if (this._userConnectedHookId !== null && typeof Hooks !== 'undefined') {
|
||||||
Hooks.off('userConnected', this._userConnectedHookId);
|
Hooks.off('userConnected', this._userConnectedHookId);
|
||||||
@@ -905,6 +1009,143 @@ export class ScryingPoolStrip extends _AppBase {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Re-order participants (Drag & Drop) ─────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles drag start on a participant item.
|
||||||
|
* @param {DragEvent} e
|
||||||
|
*/
|
||||||
|
_onDragStart(e) {
|
||||||
|
const item = e.target.closest('.sp-strip__participant-item');
|
||||||
|
if (!item) return;
|
||||||
|
const userId = item.querySelector('[data-user-id]')?.dataset?.userId;
|
||||||
|
if (!userId) return;
|
||||||
|
e.dataTransfer.setData('text/plain', userId);
|
||||||
|
e.dataTransfer.effectAllowed = 'move';
|
||||||
|
item.classList.add('sp-dragging');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles drag over on a participant item — shows drop indicator.
|
||||||
|
* @param {DragEvent} e
|
||||||
|
*/
|
||||||
|
_onDragOver(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.dataTransfer.dropEffect = 'move';
|
||||||
|
const item = e.target.closest('.sp-strip__participant-item');
|
||||||
|
if (!item) return;
|
||||||
|
item.classList.add('sp-drag-over');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles drop — reorders participants and persists the order.
|
||||||
|
* @param {DragEvent} e
|
||||||
|
*/
|
||||||
|
_onDrop(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const fromUserId = e.dataTransfer.getData('text/plain');
|
||||||
|
const toItem = e.target.closest('.sp-strip__participant-item');
|
||||||
|
if (!toItem || !fromUserId) return;
|
||||||
|
const toUserId = toItem.querySelector('[data-user-id]')?.dataset?.userId;
|
||||||
|
if (!toUserId || fromUserId === toUserId) return;
|
||||||
|
|
||||||
|
const el = this.element;
|
||||||
|
if (!el) return;
|
||||||
|
const items = [...el.querySelectorAll('.sp-strip__participant-item')];
|
||||||
|
const userIds = items.map(item => item.querySelector('[data-user-id]')?.dataset?.userId).filter(Boolean);
|
||||||
|
const fromIdx = userIds.indexOf(fromUserId);
|
||||||
|
const toIdx = userIds.indexOf(toUserId);
|
||||||
|
if (fromIdx === -1 || toIdx === -1) return;
|
||||||
|
|
||||||
|
userIds.splice(fromIdx, 1);
|
||||||
|
const adjustedTo = fromIdx < toIdx ? toIdx - 1 : toIdx;
|
||||||
|
userIds.splice(adjustedTo, 0, fromUserId);
|
||||||
|
|
||||||
|
this._saveParticipantOrder(userIds);
|
||||||
|
if (typeof this.render === 'function') {
|
||||||
|
this.render({ force: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles drag end — removes visual indicators.
|
||||||
|
*/
|
||||||
|
_onDragEnd() {
|
||||||
|
this.element?.querySelectorAll('.sp-strip__participant-item')?.forEach(item => {
|
||||||
|
item.classList.remove('sp-dragging', 'sp-drag-over');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles drag leave — removes drop indicator from the exited item.
|
||||||
|
* @param {DragEvent} e
|
||||||
|
*/
|
||||||
|
_onDragLeave(e) {
|
||||||
|
const item = e.target.closest('.sp-strip__participant-item');
|
||||||
|
if (item) item.classList.remove('sp-drag-over');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Persists participant order to GM user flag.
|
||||||
|
* @param {string[]} orderedUserIds
|
||||||
|
*/
|
||||||
|
_saveParticipantOrder(orderedUserIds) {
|
||||||
|
if (typeof game === 'undefined') return;
|
||||||
|
try {
|
||||||
|
game.user?.setFlag?.('scrying-pool', 'participantOrder', orderedUserIds);
|
||||||
|
} catch (_e) { /* no-op */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears saved order — next render uses connection order.
|
||||||
|
*/
|
||||||
|
_resetParticipantOrder() {
|
||||||
|
if (typeof game === 'undefined') return;
|
||||||
|
try {
|
||||||
|
game.user?.unsetFlag?.('scrying-pool', 'participantOrder');
|
||||||
|
} catch (_e) { /* no-op */ }
|
||||||
|
if (typeof this.render === 'function') {
|
||||||
|
this.render({ force: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Spotlight / Focus ────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles focus on a participant. If already focused, exits focus mode.
|
||||||
|
* @param {string} userId
|
||||||
|
*/
|
||||||
|
_toggleFocus(userId) {
|
||||||
|
if (this._focusedUserId === userId) {
|
||||||
|
this._focusedUserId = null;
|
||||||
|
} else {
|
||||||
|
this._focusedUserId = userId;
|
||||||
|
}
|
||||||
|
if (typeof this.render === 'function') {
|
||||||
|
this.render({ force: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Auto-save position ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves current strip position to GM user flag.
|
||||||
|
*/
|
||||||
|
_savePosition() {
|
||||||
|
if (typeof game === 'undefined') return;
|
||||||
|
try {
|
||||||
|
const pos = this.position;
|
||||||
|
if (pos?.left == null || pos?.top == null) return;
|
||||||
|
game.user?.setFlag?.('scrying-pool', 'stripState', {
|
||||||
|
left: pos.left,
|
||||||
|
top: pos.top,
|
||||||
|
width: pos.width ?? 240,
|
||||||
|
height: pos.height ?? 'auto',
|
||||||
|
savedAt: Date.now(),
|
||||||
|
});
|
||||||
|
} catch (_e) { /* no-op */ }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows a notification with i18n support and safe fallback for test environments.
|
* Shows a notification with i18n support and safe fallback for test environments.
|
||||||
* @param {'info'|'warn'|'error'} level
|
* @param {'info'|'warn'|'error'} level
|
||||||
|
|||||||
@@ -136,6 +136,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Small tile shell sizing (all S layouts) ─────────────────────────────────
|
||||||
|
// Base .sp-avatar__shell is 60×60 fixed — override to follow --sp-widget-width
|
||||||
|
.scrying-pool.scrying-pool-strip.sp-layout-vertical-sm,
|
||||||
|
.scrying-pool.scrying-pool-strip.sp-layout-horizontal-sm,
|
||||||
|
.scrying-pool.scrying-pool-strip.sp-layout-mosaic-sm {
|
||||||
|
.sp-participant-avatar .sp-avatar__shell {
|
||||||
|
width: calc(var(--sp-widget-width) - 12px);
|
||||||
|
height: calc(var(--sp-widget-width) - 12px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Toolbar: grip + toggle + DB + close on one line ─────────────────────────
|
// ── Toolbar: grip + toggle + DB + close on one line ─────────────────────────
|
||||||
// All chrome lives here; hidden at rest, revealed on strip hover.
|
// All chrome lives here; hidden at rest, revealed on strip hover.
|
||||||
.sp-strip__toolbar {
|
.sp-strip__toolbar {
|
||||||
@@ -501,6 +512,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Spotlight focus state — gold ring (uses urgency-director token)
|
||||||
|
// ============================================================
|
||||||
|
.sp-participant-avatar.sp-state-focused {
|
||||||
|
.sp-avatar__shell::after {
|
||||||
|
box-shadow: inset 0 0 0 2px var(--sp-urgency-director);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Drag-and-drop reorder feedback
|
||||||
|
// ============================================================
|
||||||
|
.sp-strip__participant-item.sp-dragging {
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sp-strip__participant-item.sp-drag-over {
|
||||||
|
box-shadow: inset 0 0 0 2px var(--sp-urgency-director);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// StateRing animations — gated under no-preference (AC-16)
|
// StateRing animations — gated under no-preference (AC-16)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
@@ -689,6 +689,12 @@
|
|||||||
.scrying-pool.scrying-pool-strip.sp-layout-mosaic-md .sp-participant-avatar::after {
|
.scrying-pool.scrying-pool-strip.sp-layout-mosaic-md .sp-participant-avatar::after {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
.scrying-pool.scrying-pool-strip.sp-layout-vertical-sm .sp-participant-avatar .sp-avatar__shell,
|
||||||
|
.scrying-pool.scrying-pool-strip.sp-layout-horizontal-sm .sp-participant-avatar .sp-avatar__shell,
|
||||||
|
.scrying-pool.scrying-pool-strip.sp-layout-mosaic-sm .sp-participant-avatar .sp-avatar__shell {
|
||||||
|
width: calc(var(--sp-widget-width) - 12px);
|
||||||
|
height: calc(var(--sp-widget-width) - 12px);
|
||||||
|
}
|
||||||
.sp-strip__toolbar {
|
.sp-strip__toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|||||||
@@ -120,7 +120,7 @@
|
|||||||
<div class="directors-board__widget-width-row">
|
<div class="directors-board__widget-width-row">
|
||||||
<span>{{localize "scrying-pool.directorsBoard.widgetWidth.small"}}</span>
|
<span>{{localize "scrying-pool.directorsBoard.widgetWidth.small"}}</span>
|
||||||
<select class="directors-board__widget-width-select" data-action="set-widget-width-sm" data-selected="{{widgetWidthSm}}">
|
<select class="directors-board__widget-width-select" data-action="set-widget-width-sm" data-selected="{{widgetWidthSm}}">
|
||||||
{{#each widthOptions}}
|
{{#each smWidthOptions}}
|
||||||
<option value="{{this.value}}">{{this.label}}</option>
|
<option value="{{this.value}}">{{this.label}}</option>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</select>
|
</select>
|
||||||
@@ -128,7 +128,7 @@
|
|||||||
<div class="directors-board__widget-width-row">
|
<div class="directors-board__widget-width-row">
|
||||||
<span>{{localize "scrying-pool.directorsBoard.widgetWidth.large"}}</span>
|
<span>{{localize "scrying-pool.directorsBoard.widgetWidth.large"}}</span>
|
||||||
<select class="directors-board__widget-width-select" data-action="set-widget-width-md" data-selected="{{widgetWidthMd}}">
|
<select class="directors-board__widget-width-select" data-action="set-widget-width-md" data-selected="{{widgetWidthMd}}">
|
||||||
{{#each widthOptions}}
|
{{#each mdWidthOptions}}
|
||||||
<option value="{{this.value}}">{{this.label}}</option>
|
<option value="{{this.value}}">{{this.label}}</option>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</select>
|
</select>
|
||||||
@@ -137,6 +137,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="directors-board__footer">
|
<footer class="directors-board__footer">
|
||||||
|
<div class="directors-board__footer-group directors-board__footer-group--strip">
|
||||||
|
<button type="button" class="directors-board__footer-btn directors-board__footer-btn--secondary" data-action="reset-strip-position"
|
||||||
|
data-tooltip="{{localize "scrying-pool.directorsBoard.footer.resetStrip"}}">
|
||||||
|
<i class="fas fa-undo-alt" aria-hidden="true"></i>
|
||||||
|
{{localize "scrying-pool.directorsBoard.footer.resetStrip"}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="directors-board__footer-group directors-board__footer-group--presets">
|
<div class="directors-board__footer-group directors-board__footer-group--presets">
|
||||||
<button type="button" class="directors-board__footer-btn" data-action="save-preset"
|
<button type="button" class="directors-board__footer-btn" data-action="save-preset"
|
||||||
data-tooltip="{{localize "scrying-pool.directorsBoard.footer.savePreset"}}">
|
data-tooltip="{{localize "scrying-pool.directorsBoard.footer.savePreset"}}">
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
{{#each participants}}
|
{{#each participants}}
|
||||||
<li class="sp-strip__participant-item" role="listitem">
|
<li class="sp-strip__participant-item" role="listitem">
|
||||||
{{!-- ParticipantAvatar (44×44px container) --}}
|
{{!-- ParticipantAvatar (44×44px container) --}}
|
||||||
<button class="sp-participant-avatar sp-state-{{state}}{{#if hasPendingOp}} sp-state-pending{{/if}} sp-shape-{{../tileShape}}"
|
<button class="sp-participant-avatar sp-state-{{state}}{{#if hasPendingOp}} sp-state-pending{{/if}}{{#if isFocused}} sp-state-focused{{/if}} sp-shape-{{../tileShape}}"
|
||||||
data-user-id="{{userId}}"
|
data-user-id="{{userId}}"
|
||||||
data-action="open-popover"
|
data-action="open-popover"
|
||||||
role="button"
|
role="button"
|
||||||
|
|||||||
Reference in New Issue
Block a user