|
| 1 | +# Item Image Popout Implementation Plan |
| 2 | + |
| 3 | +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. |
| 4 | +
|
| 5 | +**Goal:** Create the `coc7-qol` FoundryVTT module that lets non-GM players click item sheet images to view them full-size in an ImagePopout window. |
| 6 | + |
| 7 | +**Architecture:** A minimal FoundryVTT module with a single ES module that registers a `renderItemSheet` hook. The hook finds `img[data-edit="img"]` elements, strips the edit attribute for non-GM users, and attaches a click handler that opens `ImagePopout`. Must support both FoundryVTT v12 (ApplicationV1 + jQuery) and v13 (ApplicationV2 + HTMLElement). |
| 8 | + |
| 9 | +**Tech Stack:** Vanilla JavaScript ES modules, FoundryVTT Hooks API, FoundryVTT ImagePopout class. |
| 10 | + |
| 11 | +--- |
| 12 | + |
| 13 | +## File Structure |
| 14 | + |
| 15 | +``` |
| 16 | +coc7-qol/ |
| 17 | +├── module.json # Module manifest |
| 18 | +└── scripts/ |
| 19 | + └── item-image-popout.js # Hook registration and click handler logic |
| 20 | +``` |
| 21 | + |
| 22 | +- `module.json` — Module identity, compatibility, system restriction, ES module entry point. |
| 23 | +- `scripts/item-image-popout.js` — Single file: registers the `renderItemSheet` hook, guards on `!game.user.isGM`, modifies the image element, attaches the popout click handler. Handles v12/v13 API differences. |
| 24 | + |
| 25 | +--- |
| 26 | + |
| 27 | +### Task 1: Create module.json manifest |
| 28 | + |
| 29 | +**Files:** |
| 30 | +- Create: `module.json` |
| 31 | + |
| 32 | +- [ ] **Step 1: Create `module.json`** |
| 33 | + |
| 34 | +```json |
| 35 | +{ |
| 36 | + "id": "coc7-qol", |
| 37 | + "title": "CoC7 QoL Improvements", |
| 38 | + "description": "Quality of life improvements for the Call of Cthulhu 7th Edition system. Adds image popout on item sheets for players.", |
| 39 | + "version": "0.1.0", |
| 40 | + "authors": [ |
| 41 | + { |
| 42 | + "name": "Martin Papy" |
| 43 | + } |
| 44 | + ], |
| 45 | + "compatibility": { |
| 46 | + "minimum": "12", |
| 47 | + "verified": "13" |
| 48 | + }, |
| 49 | + "relationships": { |
| 50 | + "systems": [ |
| 51 | + { |
| 52 | + "id": "CoC7", |
| 53 | + "type": "system" |
| 54 | + } |
| 55 | + ] |
| 56 | + }, |
| 57 | + "esmodules": [ |
| 58 | + "scripts/item-image-popout.js" |
| 59 | + ] |
| 60 | +} |
| 61 | +``` |
| 62 | + |
| 63 | +- [ ] **Step 2: Commit** |
| 64 | + |
| 65 | +```bash |
| 66 | +git add module.json |
| 67 | +git commit -m "feat: add module.json manifest for coc7-qol" |
| 68 | +``` |
| 69 | + |
| 70 | +--- |
| 71 | + |
| 72 | +### Task 2: Create item-image-popout.js |
| 73 | + |
| 74 | +**Files:** |
| 75 | +- Create: `scripts/item-image-popout.js` |
| 76 | + |
| 77 | +- [ ] **Step 1: Create `scripts/item-image-popout.js`** |
| 78 | + |
| 79 | +```javascript |
| 80 | +Hooks.on('renderItemSheet', (sheet, html, data) => { |
| 81 | + if (game.user.isGM) return; |
| 82 | + |
| 83 | + // html is jQuery in v12, HTMLElement in v13 |
| 84 | + const element = html instanceof jQuery ? html[0] : html; |
| 85 | + const img = element.querySelector('img[data-edit="img"]'); |
| 86 | + if (!img) return; |
| 87 | + |
| 88 | + // Remove data-edit so Foundry's file picker doesn't intercept clicks |
| 89 | + img.removeAttribute('data-edit'); |
| 90 | + img.style.cursor = 'pointer'; |
| 91 | + |
| 92 | + img.addEventListener('click', (event) => { |
| 93 | + event.preventDefault(); |
| 94 | + event.stopPropagation(); |
| 95 | + |
| 96 | + const src = img.getAttribute('src'); |
| 97 | + const title = sheet.object.name; |
| 98 | + |
| 99 | + // v13 uses ApplicationV2-style options, v12 uses positional args |
| 100 | + if (foundry.applications?.apps?.ImagePopout) { |
| 101 | + new foundry.applications.apps.ImagePopout({ |
| 102 | + src: src, |
| 103 | + window: { title: title } |
| 104 | + }).render(true); |
| 105 | + } else { |
| 106 | + new ImagePopout(src, { title: title }).render(true); |
| 107 | + } |
| 108 | + }); |
| 109 | +}); |
| 110 | +``` |
| 111 | +
|
| 112 | +- [ ] **Step 2: Commit** |
| 113 | +
|
| 114 | +```bash |
| 115 | +git add scripts/item-image-popout.js |
| 116 | +git commit -m "feat: add item image popout for non-GM players" |
| 117 | +``` |
| 118 | +
|
| 119 | +--- |
| 120 | +
|
| 121 | +### Task 3: Manual testing |
| 122 | +
|
| 123 | +No automated tests — this is a FoundryVTT UI module that requires a running game server. Manual verification steps: |
| 124 | +
|
| 125 | +- [ ] **Step 1: Install module in FoundryVTT** |
| 126 | +
|
| 127 | +Symlink or copy the `coc7-qol/` folder into your FoundryVTT modules directory: |
| 128 | +
|
| 129 | +```bash |
| 130 | +ln -s /Users/martin.papy/Development/coc7-qol "$FOUNDRY_DATA_PATH/Data/modules/coc7-qol" |
| 131 | +``` |
| 132 | +
|
| 133 | +Replace `$FOUNDRY_DATA_PATH` with your actual FoundryVTT user data path. |
| 134 | +
|
| 135 | +- [ ] **Step 2: Activate and test as player** |
| 136 | +
|
| 137 | +1. Launch FoundryVTT, open a world using the CoC7 system. |
| 138 | +2. Go to Settings > Manage Modules > enable "CoC7 QoL Improvements". |
| 139 | +3. Log in as a **player** (non-GM). |
| 140 | +4. Open any item sheet (weapon, spell, book, skill, generic item, etc.). |
| 141 | +5. Click the item image — verify an `ImagePopout` window opens showing the full image. |
| 142 | +6. Verify the image is displayed in a draggable, resizable Foundry window. |
| 143 | +
|
| 144 | +- [ ] **Step 3: Test as GM** |
| 145 | +
|
| 146 | +1. Log in as **GM**. |
| 147 | +2. Open any item sheet. |
| 148 | +3. Click the item image — verify the **file picker** opens (default behavior, unchanged). |
| 149 | +
|
| 150 | +- [ ] **Step 4: Test edge cases** |
| 151 | +
|
| 152 | +1. Open an item with the default/placeholder image — verify popout still opens (no crash). |
| 153 | +2. Open multiple item sheets and click images — verify each opens its own popout with the correct title. |
| 154 | +3. Close and reopen an item sheet — verify the click handler still works (re-render). |
0 commit comments