api canvas - Drowbe/coffee-pub-blacksmith GitHub Wiki
Blacksmith Canvas Layer API Documentation
Audience: Developers integrating with Blacksmith and leveraging the exposed API.
Overview
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.
Getting Started
1. Access the API
// 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
}
});
2. Check Availability
// Check if layer is available
const layer = await BlacksmithAPI.getCanvasLayer();
if (!layer) {
console.warn('BlacksmithLayer not available yet - ensure canvas is ready');
return;
}
API Reference
Canvas Layer Access
BlacksmithAPI.getCanvasLayer()
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();
}
Direct API Access
const blacksmith = await BlacksmithAPI.get();
const layer = blacksmith.CanvasLayer; // null on initial load — prefer getCanvasLayer(), see below
Prefer 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.
Direct Canvas Access
Hooks.once('canvasReady', () => {
const layer = canvas['blacksmith-utilities-layer'];
if (layer) {
// Use the layer
}
});
Global Access (after canvasReady)
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
}
Layer Properties
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
Use Cases
1. Temporary Drawing Management
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];
2. UI Overlays
Use BlacksmithLayer for drawing-related UI elements:
const layer = await BlacksmithAPI.getCanvasLayer();
// Layer extends foundry.canvas.layers.CanvasLayer
// Override _draw() method for custom rendering
3. Drawing Cleanup
Coordinate 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();
});
}
Availability
Timing
- 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
Checking Availability
// 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');
}
});
Examples
Complete Example: Temporary Drawing Module
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];
}
Best Practices
-
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); }
Troubleshooting
Layer Not Available
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();
});
Drawings Persisting When They Shouldn't
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 = false
Permission Issues
Problem: Drawings not appearing or being removed
Solutions:
- Check user permissions
- Verify module settings
- Ensure proper scene ownership
Related Documentation
- Core API - Blacksmith's shared utilities and constants
- Canvas layer implementation:
scripts/canvas-layer.js
Quick check
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.