Repository navigation
api canvas
Audience: Developers integrating with Blacksmith and leveraging the exposed API.
The Blacksmith Canvas Layer API provides access to the BlacksmithLayer, a custom canvas layer that enables centralized canvas management for Coffee Pub modules. This layer is ideal for temporary drawings, UI overlays, and coordinated canvas interactions.
// Import the Blacksmith API bridge
import { BlacksmithAPI } from '/modules/coffee-pub-blacksmith/api/blacksmith-api.js';
// After canvas is ready
Hooks.once('canvasReady', async () => {
const blacksmithLayer = await BlacksmithAPI.getCanvasLayer();
if (blacksmithLayer) {
// Layer is ready to use
}
});// Check if layer is available
const layer = await BlacksmithAPI.getCanvasLayer();
if (!layer) {
console.warn('BlacksmithLayer not available yet - ensure canvas is ready');
return;
}Returns the BlacksmithLayer instance if available.
Returns: Promise<Object|null> - BlacksmithLayer instance or null if not ready
Example:
const layer = await BlacksmithAPI.getCanvasLayer();
if (layer) {
layer.activate();
}const blacksmith = await BlacksmithAPI.get();
const layer = blacksmith.CanvasLayer; // null on initial load — prefer getCanvasLayer(), see belowPrefer BlacksmithAPI.getCanvasLayer(). blacksmith.CanvasLayer is unreliable:
- It is
nullon the initial canvas draw, and only becomes populated after a scene switch. - It is gated on the
enableSceneClickBehaviorssetting (default on); with that setting off,blacksmith.CanvasLayerstaysnulleven across scene switches. -
window.BlacksmithCanvasLayeris effectively never set — a sync step assigns it only whileblacksmith.CanvasLayeris stillnull, and nothing re-syncs it.
BlacksmithAPI.getCanvasLayer() works in every case: it returns api.getCanvasLayer() when present, and otherwise reads the layer straight off the canvas (canvas['blacksmith-utilities-layer']), which is registered unconditionally at init.
Hooks.once('canvasReady', () => {
const layer = canvas['blacksmith-utilities-layer'];
if (layer) {
// Use the layer
}
});window.BlacksmithCanvasLayer is effectively never set (see above), so this block is a silent no-op. Use BlacksmithAPI.getCanvasLayer() or read canvas['blacksmith-utilities-layer'] directly.
if (window.BlacksmithCanvasLayer) {
const layer = window.BlacksmithCanvasLayer;
// Use the layer
}The BlacksmithLayer extends foundry.canvas.layers.CanvasLayer and provides:
-
Standard Canvas Layer Methods:
activate(),deactivate(),_draw() - Centralized Management: Single layer for all Coffee Pub canvas interactions
- Event Coordination: Shared event handling for canvas operations
- UI Overlay Support: Custom rendering capabilities
Perfect for modules like Cartographer that need to create temporary player drawings:
// Access the layer
const layer = await BlacksmithAPI.getCanvasLayer();
// Create temporary drawings with flags
const drawings = await canvas.scene.createEmbeddedDocuments("Drawing", [{
type: "f", // freehand
author: game.user.id,
x: startX,
y: startY,
points: [[x1, y1], [x2, y2], ...],
strokeWidth: brushSize,
strokeColor: brushColor,
flags: {
"your-module-id": {
temporary: true,
layerManaged: true,
playerDrawn: true,
expiresAt: Date.now() + (timeout * 1000)
}
}
}]);
const drawing = drawings[0];Use BlacksmithLayer for drawing-related UI elements:
const layer = await BlacksmithAPI.getCanvasLayer();
// Layer extends foundry.canvas.layers.CanvasLayer
// Override _draw() method for custom renderingCoordinate cleanup of temporary canvas elements:
Hooks.on("updateScene", async () => {
const layer = await BlacksmithAPI.getCanvasLayer();
if (layer) {
// Cleanup temporary elements
clearTemporaryDrawings();
}
});
function clearTemporaryDrawings() {
const temporaryDrawings = canvas.drawings.placeables.filter(d =>
d.flags?.['your-module-id']?.temporary === true
);
temporaryDrawings.forEach(drawing => {
drawing.delete();
});
}- Initialization: BlacksmithLayer is created during Blacksmith initialization
-
Availability: Layer is available after
canvasReadyhook fires - Persistence: Layer persists across scene changes
- Group: Part of Foundry's "interface" layer group
// Wait for canvas to be ready
Hooks.once('canvasReady', async () => {
const layer = await BlacksmithAPI.getCanvasLayer();
if (layer) {
// Safe to use layer
console.log('BlacksmithLayer is ready');
} else {
console.warn('BlacksmithLayer not available');
}
});import { BlacksmithAPI } from '/modules/coffee-pub-blacksmith/api/blacksmith-api.js';
Hooks.once('canvasReady', async () => {
const layer = await BlacksmithAPI.getCanvasLayer();
if (!layer) {
console.error('BlacksmithLayer not available');
return;
}
// Setup cleanup on scene change
Hooks.on("updateScene", () => {
clearTemporaryDrawings();
});
});
function clearTemporaryDrawings() {
const temporaryDrawings = canvas.drawings.placeables.filter(d =>
d.flags?.[MODULE.ID]?.temporary === true
);
temporaryDrawings.forEach(drawing => {
drawing.delete();
});
}
async function createTemporaryDrawing(startX, startY, points, brushSize, brushColor) {
const drawings = await canvas.scene.createEmbeddedDocuments("Drawing", [{
type: "f", // freehand
author: game.user.id,
x: startX,
y: startY,
points: points,
strokeWidth: brushSize,
strokeColor: brushColor,
flags: {
[MODULE.ID]: {
temporary: true,
layerManaged: true,
playerDrawn: true,
expiresAt: Date.now() + (3600 * 1000) // 1 hour
}
}
}]);
return drawings[0];
}-
Check Availability: Always verify layer exists before use
const layer = await BlacksmithAPI.getCanvasLayer(); if (!layer) return;
-
Wait for canvasReady: Layer is only available after canvas initialization
Hooks.once('canvasReady', async () => { // Safe to access layer here });
-
Use Flags: Mark temporary drawings with flags for easy identification
flags: { [MODULE.ID]: { temporary: true, layerManaged: true } }
-
Coordinate Cleanup: Use layer for centralized cleanup management
Hooks.on("updateScene", clearTemporaryDrawings);
-
Respect Other Modules: Coordinate with other modules using the layer
// Check for conflicts with other modules const existingDrawings = canvas.drawings.placeables.filter(d => d.flags?.['other-module-id']?.temporary );
-
Error Handling: Always handle cases where layer might not be available
try { const layer = await BlacksmithAPI.getCanvasLayer(); if (!layer) { console.warn('Layer not available, using fallback'); // Fallback implementation } } catch (error) { console.error('Error accessing BlacksmithLayer:', error); }
Problem: getCanvasLayer() returns null
Solutions:
- Ensure you're accessing after
canvasReadyhook fires - Check that Blacksmith module is enabled
- Verify canvas has initialized properly
Hooks.once('canvasReady', async () => {
// Wait a tick to ensure layer is fully initialized
await new Promise(resolve => setTimeout(resolve, 100));
const layer = await BlacksmithAPI.getCanvasLayer();
});Problem: Temporary drawings persist after cleanup
Solutions:
- Verify
flags[MODULE.ID].temporary === true - Check cleanup hooks are registered correctly
- Ensure drawings are deleted, not just hidden
// Verify flag structure
console.log(drawing.flags?.[MODULE.ID]?.temporary); // Should be true
// Ensure proper deletion
await drawing.delete(); // Not just drawing.visible = falseProblem: Drawings not appearing or being removed
Solutions:
- Check user permissions
- Verify module settings
- Ensure proper scene ownership
- Core API - Blacksmith's shared utilities and constants
- Canvas layer implementation:
scripts/canvas-layer.js
After canvasReady, in the browser console:
canvas['blacksmith-utilities-layer'] // direct access
await BlacksmithAPI.getCanvasLayer() // via the bridge (recommended)Both return the BlacksmithLayer instance once the canvas is ready. If they return null, confirm the canvas has initialized and Blacksmith is enabled.
- Ai assistance
- Applicationv2 traps
- CSS traps
- dnd5e conditions
- Documentation standard
- Token actor identity
- Campaign
- Canvas
- Chatcards
- Compendiums
- Contextmenu
- Core
- Create journal entry
- Dialog
- Active Effects
- Entity list
- Equiplocations
- Geography
- GM request
- Gmnotes
- Health
- Hookmanager
- Importer
- Inventory
- Menubar
- Notes
- Party
- Pins
- Quantity split
- Requestroll
- Rolls
- Scene config
- Sockets
- Stats
- Tags
- Toast
- Tokens
- Toolbar
- Window
- Worldclock