mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-18 10:01:23 +01:00
Add external API integration for wiki/web UI synchronization
This commit adds a comprehensive external API system that allows external tools (wikis, web UIs, etc.) to control and synchronize with Fantasy Map Generator. New Features: - External API Bridge (modules/external-api.js) - Event-driven architecture with EventEmitter - Map lifecycle control (create, load, save) - Data access methods (rivers, cultures, states, burgs) - Data mutation methods with auto-redraw - Export support (SVG, PNG, JSON) - Change detection with automatic event emission - PostMessage Communication Layer - Auto-enables when FMG is embedded in iframe - Bidirectional message passing - Request/response pattern with promise support - Automatic event forwarding to parent window - REST API Server (api-server/) - Express.js server with full CRUD operations - WebSocket support via Socket.IO for real-time updates - File upload support for map and CSV import - In-memory storage (can be replaced with database) - CORS enabled for cross-origin requests - Comprehensive endpoints for all map data - Client Library (api-server/client.js) - Simple JavaScript client for REST API - Promise-based async methods - Works in browser and Node.js - Demo Pages (demos/) - PostMessage integration demo with full UI - REST API demo with interactive testing - WebSocket demo for real-time events - Documentation - Comprehensive integration guide (EXTERNAL_API_INTEGRATION.md) - API reference with TypeScript interfaces - Multiple integration examples - Troubleshooting guide Integration Methods: 1. PostMessage Bridge - For iframe embedding 2. REST API - For server-side integration 3. Direct JavaScript API - For same-origin apps Use Cases: - Wiki pages that need to display and control maps - Web UIs that want to edit map data - External tools that need to sync with FMG - Real-time collaborative map editing - Batch operations and automation Technical Details: - Zero dependencies for external-api.js (pure JS) - Auto-initializes on DOMContentLoaded - Throttled change detection (500ms debounce) - Deep cloning for data access (prevents mutations) - Error handling throughout - Version tagged (v1.0.0) Updated Files: - index.html: Added script tag to load external-api module All APIs are backward compatible and don't affect existing functionality.
This commit is contained in:
parent
dede314c94
commit
20458e39e2
10 changed files with 4699 additions and 0 deletions
260
api-server/client.js
Normal file
260
api-server/client.js
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
/**
|
||||
* Fantasy Map Generator - JavaScript Client Library
|
||||
*
|
||||
* A simple client library for interacting with the FMG REST API
|
||||
*
|
||||
* Usage:
|
||||
* const client = new FMGClient('http://localhost:3000/api');
|
||||
* const map = await client.createMap({ seed: 'my-world' });
|
||||
*/
|
||||
|
||||
class FMGClient {
|
||||
constructor(baseUrl = 'http://localhost:3000/api') {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make API request
|
||||
* @private
|
||||
*/
|
||||
async _request(endpoint, options = {}) {
|
||||
const url = `${this.baseUrl}${endpoint}`;
|
||||
const config = {
|
||||
method: options.method || 'GET',
|
||||
headers: {
|
||||
...options.headers
|
||||
}
|
||||
};
|
||||
|
||||
if (options.body) {
|
||||
if (options.body instanceof FormData) {
|
||||
config.body = options.body;
|
||||
} else {
|
||||
config.headers['Content-Type'] = 'application/json';
|
||||
config.body = JSON.stringify(options.body);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(url, config);
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.error || `HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
throw new Error(`API request failed: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// HEALTH
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Check API health
|
||||
* @returns {Promise<Object>} Health status
|
||||
*/
|
||||
async health() {
|
||||
return this._request('/health');
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MAP OPERATIONS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Create a new map
|
||||
* @param {Object} options - Map options
|
||||
* @param {string} options.seed - Map seed
|
||||
* @param {number} options.width - Map width
|
||||
* @param {number} options.height - Map height
|
||||
* @returns {Promise<Object>} Created map info
|
||||
*/
|
||||
async createMap(options = {}) {
|
||||
return this._request('/maps', {
|
||||
method: 'POST',
|
||||
body: options
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get map by ID
|
||||
* @param {string} mapId - Map ID
|
||||
* @returns {Promise<Object>} Map data
|
||||
*/
|
||||
async getMap(mapId) {
|
||||
return this._request(`/maps/${mapId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all maps
|
||||
* @returns {Promise<Object>} Map list
|
||||
*/
|
||||
async listMaps() {
|
||||
return this._request('/maps');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update map
|
||||
* @param {string} mapId - Map ID
|
||||
* @param {Object} data - Map data
|
||||
* @returns {Promise<Object>} Updated map
|
||||
*/
|
||||
async updateMap(mapId, data) {
|
||||
return this._request(`/maps/${mapId}`, {
|
||||
method: 'PUT',
|
||||
body: { data }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete map
|
||||
* @param {string} mapId - Map ID
|
||||
* @returns {Promise<Object>} Deletion result
|
||||
*/
|
||||
async deleteMap(mapId) {
|
||||
return this._request(`/maps/${mapId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load map from file
|
||||
* @param {string} mapId - Map ID
|
||||
* @param {File} file - Map file
|
||||
* @returns {Promise<Object>} Load result
|
||||
*/
|
||||
async loadMap(mapId, file) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
return this._request(`/maps/${mapId}/load`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// RIVERS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Get rivers
|
||||
* @param {string} mapId - Map ID
|
||||
* @returns {Promise<Array>} Rivers array
|
||||
*/
|
||||
async getRivers(mapId) {
|
||||
const result = await this._request(`/maps/${mapId}/rivers`);
|
||||
return result.rivers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update rivers
|
||||
* @param {string} mapId - Map ID
|
||||
* @param {Array} rivers - Rivers array
|
||||
* @returns {Promise<Object>} Update result
|
||||
*/
|
||||
async updateRivers(mapId, rivers) {
|
||||
return this._request(`/maps/${mapId}/rivers`, {
|
||||
method: 'PUT',
|
||||
body: { rivers }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Import rivers from CSV
|
||||
* @param {string} mapId - Map ID
|
||||
* @param {File} csvFile - CSV file
|
||||
* @returns {Promise<Object>} Import result
|
||||
*/
|
||||
async importRiversCSV(mapId, csvFile) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', csvFile);
|
||||
|
||||
return this._request(`/maps/${mapId}/rivers/import`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CULTURES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Get cultures
|
||||
* @param {string} mapId - Map ID
|
||||
* @returns {Promise<Array>} Cultures array
|
||||
*/
|
||||
async getCultures(mapId) {
|
||||
const result = await this._request(`/maps/${mapId}/cultures`);
|
||||
return result.cultures;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// STATES
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Get states
|
||||
* @param {string} mapId - Map ID
|
||||
* @returns {Promise<Array>} States array
|
||||
*/
|
||||
async getStates(mapId) {
|
||||
const result = await this._request(`/maps/${mapId}/states`);
|
||||
return result.states;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// BURGS
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Get burgs (cities/towns)
|
||||
* @param {string} mapId - Map ID
|
||||
* @returns {Promise<Array>} Burgs array
|
||||
*/
|
||||
async getBurgs(mapId) {
|
||||
const result = await this._request(`/maps/${mapId}/burgs`);
|
||||
return result.burgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new burg
|
||||
* @param {string} mapId - Map ID
|
||||
* @param {Object} burgData - Burg data
|
||||
* @returns {Promise<Object>} Created burg
|
||||
*/
|
||||
async addBurg(mapId, burgData) {
|
||||
return this._request(`/maps/${mapId}/burgs`, {
|
||||
method: 'POST',
|
||||
body: burgData
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// EXPORT
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Export map in specified format
|
||||
* @param {string} mapId - Map ID
|
||||
* @param {string} format - Export format (svg, png, json, data)
|
||||
* @returns {Promise<Object>} Export result
|
||||
*/
|
||||
async exportMap(mapId, format) {
|
||||
return this._request(`/maps/${mapId}/export/${format}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Export for Node.js and browser
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = FMGClient;
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
window.FMGClient = FMGClient;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue