diff --git a/.claude/commands/dev.md b/.claude/commands/dev.md index 327dbd7..451e114 100644 --- a/.claude/commands/dev.md +++ b/.claude/commands/dev.md @@ -11,6 +11,6 @@ cd /mnt/c/Users/markr/Documents/KB/journal/.obsidian/plugins/duckmage-plugin && Run this in the background — it is a long-running process. Tell the user the watcher has started, then remind them: - `main.js` rebuilds automatically on every save to `main.ts` -- To reload the plugin in Obsidian after a rebuild: open the developer console (Ctrl+Shift+I) and run: - `app.plugins.disablePlugin('duckmage-plugin'); app.plugins.enablePlugin('duckmage-plugin');` +- To reload the plugin in Obsidian after a rebuild, run: + `powershell.exe -Command "obsidian plugin:reload id=duckmage-plugin"` - Or use `/rebuild` instead for a one-off production build (includes type-checking). diff --git a/.claude/worktrees/agent-ad301d84/src/constants.ts b/.claude/worktrees/agent-ad301d84/src/constants.ts index 2a04797..ee602c3 100644 --- a/.claude/worktrees/agent-ad301d84/src/constants.ts +++ b/.claude/worktrees/agent-ad301d84/src/constants.ts @@ -1,129 +1,129 @@ -import type { TerrainColor, DuckmagePluginSettings } from "./types"; - -export const VIEW_TYPE_HEX_MAP = "duckmage-hex-map"; -export const VIEW_TYPE_HEX_TABLE = "duckmage-hex-table"; -export const VIEW_TYPE_RANDOM_TABLES = "duckmage-random-tables"; - -export const DEFAULT_TERRAIN_PALETTE: TerrainColor[] = [ - // Water - { name: "ocean", color: "#29507f" }, - { name: "shallows", color: "#4a82a5" }, - { name: "water", color: "#60a5fa" }, - // Open - { name: "grass", color: "#638c7b", icon: "bw-grassland.png" }, - { name: "hills", color: "#e0e8a1", icon: "bw-hills.png" }, - { name: "foothills", color: "#81c191", icon: "bw-hills.png" }, - { name: "snow", color: "#e0f2fe" }, - { name: "beach", color: "#cac181", icon: "bw-grassland.png" }, - { name: "urban", color: "#888888" }, - // Desert - { name: "desert", color: "#ecdba2" }, - { name: "desert rocky", color: "#e6bc60", icon: "bw-desert-rocky.png" }, - { name: "dunes", color: "#eccd7e", icon: "bw-dunes.png" }, - { name: "cactus", color: "#e2b75a", icon: "bw-cactus.png" }, - { name: "cactus heavy", color: "#ddb869", icon: "bw-cactus-heavy.png" }, - { name: "badlands", color: "#c2410c", icon: "bw-badlands.png" }, - { name: "brokenlands", color: "#92400e", icon: "bw-brokenlands.png" }, - { name: "cliffs", color: "#a86f1f", icon: "bw-brokenlands.png" }, - { name: "Salt Flats", color: "#f7eaba", icon: "bw-dunes.png" }, - // Forest - { name: "forest", color: "#2d9553", icon: "bw-forest-heavy.png" }, - { name: "forest heavy", color: "#15803d", icon: "bw-forest-heavy.png" }, - { name: "forested hills", color: "#22c55e", icon: "bw-forested-hills.png" }, - { name: "mixed forest", color: "#16a34a", icon: "bw-forest-mixed.png" }, - { - name: "mixed forest heavy", - color: "#15803d", - icon: "bw-forest-mixed-heavy.png", - }, - { - name: "mixed forest hills", - color: "#22c55e", - icon: "bw-forest-mixed-hills.png", - }, - // Evergreen - { name: "evergreen", color: "#428a5e", icon: "bw-evergreen.png" }, - { name: "evergreen heavy", color: "#257445", icon: "bw-evergreen-heavy.png" }, - { name: "evergreen hills", color: "#328651", icon: "bw-evergreen-hills.png" }, - // Jungle - { name: "jungle", color: "#15803d", icon: "bw-jungle.png" }, - { name: "jungle heavy", color: "#14532d", icon: "bw-jungle-heavy.png" }, - { name: "jungle hills", color: "#4ade80", icon: "bw-jungle-hills.png" }, - // Mountains - { name: "mountain", color: "#a77649", icon: "bw-mountain.png" }, - { name: "peak", color: "#78716c", icon: "bw-mountain.png" }, - { name: "mountains snow", color: "#bfdbfe", icon: "bw-mountains-snow.png" }, - { - name: "forested mountain", - color: "#6b9e7c", - icon: "bw-forested-mountain.png", - }, - { - name: "forested mountains", - color: "#5e8c6a", - icon: "bw-forested-mountains.png", - }, - { - name: "mixed forest mountain", - color: "#6b9e7c", - icon: "bw-forest-mixed-mountain.png", - }, - { - name: "mixed forest mountains", - color: "#5e8c6a", - icon: "bw-forest-mixed-mountains.png", - }, - { - name: "evergreen mountain", - color: "#6b806b", - icon: "bw-evergreen-mountain.png", - }, - { - name: "evergreen mountains", - color: "#8c9d80", - icon: "bw-evergreen-mountains.png", - }, - { name: "jungle mountain", color: "#4d7c0f", icon: "bw-jungle-mountain.png" }, - { - name: "jungle mountains", - color: "#3f6212", - icon: "bw-jungle-mountains.png", - }, - // Volcanic - { name: "volcano", color: "#b91c1c", icon: "bw-volcano.png" }, - { name: "volcano dormant", color: "#78350f", icon: "bw-volcano-dormant.png" }, - // Wetlands - { name: "marsh", color: "#4d7c0f", icon: "bw-marsh.png" }, - { name: "swamp", color: "#365314", icon: "bw-swamp.png" }, - { name: "bog", color: "#432e6b", icon: "bw-swamp.png" }, -]; - -export const DEFAULT_SETTINGS: DuckmagePluginSettings = { - mySetting: "default", - worldFolder: "world", - hexFolder: "world/hexes", - townsFolder: "", - dungeonsFolder: "", - questsFolder: "", - featuresFolder: "", - iconsFolder: "", - templatePath: "", - hexGap: "0.15", - terrainPalette: DEFAULT_TERRAIN_PALETTE, - gridSize: { cols: 20, rows: 16 }, - gridOffset: { x: 0, y: 0 }, - zoomLevel: 1, - roadChains: [], - riverChains: [], - roadColor: "#a16207", - riverColor: "#3b82f6", - hexOrientation: "flat", - tablesFolder: "world/tables", - factionsFolder: "", - defaultTableDice: 100, - hexEditorTerrainCollapsed: false, - hexEditorFeaturesCollapsed: false, - hexEditorNotesCollapsed: false, - rollTableExcludedFolders: ["terrain"], - encounterTableExcludedFolders: ["terrain"], -}; +import type { TerrainColor, DuckmagePluginSettings } from "./types"; + +export const VIEW_TYPE_HEX_MAP = "duckmage-hex-map"; +export const VIEW_TYPE_HEX_TABLE = "duckmage-hex-table"; +export const VIEW_TYPE_RANDOM_TABLES = "duckmage-random-tables"; + +export const DEFAULT_TERRAIN_PALETTE: TerrainColor[] = [ + // Water + { name: "ocean", color: "#29507f" }, + { name: "shallows", color: "#4a82a5" }, + { name: "water", color: "#60a5fa" }, + // Open + { name: "grass", color: "#638c7b", icon: "bw-grassland.png" }, + { name: "hills", color: "#e0e8a1", icon: "bw-hills.png" }, + { name: "foothills", color: "#81c191", icon: "bw-hills.png" }, + { name: "snow", color: "#e0f2fe" }, + { name: "beach", color: "#cac181", icon: "bw-grassland.png" }, + { name: "urban", color: "#888888" }, + // Desert + { name: "desert", color: "#ecdba2" }, + { name: "desert rocky", color: "#e6bc60", icon: "bw-desert-rocky.png" }, + { name: "dunes", color: "#eccd7e", icon: "bw-dunes.png" }, + { name: "cactus", color: "#e2b75a", icon: "bw-cactus.png" }, + { name: "cactus heavy", color: "#ddb869", icon: "bw-cactus-heavy.png" }, + { name: "badlands", color: "#c2410c", icon: "bw-badlands.png" }, + { name: "brokenlands", color: "#92400e", icon: "bw-brokenlands.png" }, + { name: "cliffs", color: "#a86f1f", icon: "bw-brokenlands.png" }, + { name: "salt flats", color: "#f7eaba", icon: "bw-dunes.png" }, + // Forest + { name: "forest", color: "#2d9553", icon: "bw-forest-heavy.png" }, + { name: "forest heavy", color: "#15803d", icon: "bw-forest-heavy.png" }, + { name: "forested hills", color: "#22c55e", icon: "bw-forested-hills.png" }, + { name: "mixed forest", color: "#16a34a", icon: "bw-forest-mixed.png" }, + { + name: "mixed forest heavy", + color: "#15803d", + icon: "bw-forest-mixed-heavy.png", + }, + { + name: "mixed forest hills", + color: "#22c55e", + icon: "bw-forest-mixed-hills.png", + }, + // Evergreen + { name: "evergreen", color: "#428a5e", icon: "bw-evergreen.png" }, + { name: "evergreen heavy", color: "#257445", icon: "bw-evergreen-heavy.png" }, + { name: "evergreen hills", color: "#328651", icon: "bw-evergreen-hills.png" }, + // Jungle + { name: "jungle", color: "#15803d", icon: "bw-jungle.png" }, + { name: "jungle heavy", color: "#14532d", icon: "bw-jungle-heavy.png" }, + { name: "jungle hills", color: "#4ade80", icon: "bw-jungle-hills.png" }, + // Mountains + { name: "mountain", color: "#a77649", icon: "bw-mountain.png" }, + { name: "peak", color: "#78716c", icon: "bw-mountain.png" }, + { name: "mountains snow", color: "#bfdbfe", icon: "bw-mountains-snow.png" }, + { + name: "forested mountain", + color: "#6b9e7c", + icon: "bw-forested-mountain.png", + }, + { + name: "forested mountains", + color: "#5e8c6a", + icon: "bw-forested-mountains.png", + }, + { + name: "mixed forest mountain", + color: "#6b9e7c", + icon: "bw-forest-mixed-mountain.png", + }, + { + name: "mixed forest mountains", + color: "#5e8c6a", + icon: "bw-forest-mixed-mountains.png", + }, + { + name: "evergreen mountain", + color: "#6b806b", + icon: "bw-evergreen-mountain.png", + }, + { + name: "evergreen mountains", + color: "#8c9d80", + icon: "bw-evergreen-mountains.png", + }, + { name: "jungle mountain", color: "#4d7c0f", icon: "bw-jungle-mountain.png" }, + { + name: "jungle mountains", + color: "#3f6212", + icon: "bw-jungle-mountains.png", + }, + // Volcanic + { name: "volcano", color: "#b91c1c", icon: "bw-volcano.png" }, + { name: "volcano dormant", color: "#78350f", icon: "bw-volcano-dormant.png" }, + // Wetlands + { name: "marsh", color: "#4d7c0f", icon: "bw-marsh.png" }, + { name: "swamp", color: "#365314", icon: "bw-swamp.png" }, + { name: "bog", color: "#432e6b", icon: "bw-swamp.png" }, +]; + +export const DEFAULT_SETTINGS: DuckmagePluginSettings = { + mySetting: "default", + worldFolder: "world", + hexFolder: "world/hexes", + townsFolder: "", + dungeonsFolder: "", + questsFolder: "", + featuresFolder: "", + iconsFolder: "", + templatePath: "", + hexGap: "0.15", + terrainPalette: DEFAULT_TERRAIN_PALETTE, + gridSize: { cols: 20, rows: 16 }, + gridOffset: { x: 0, y: 0 }, + zoomLevel: 1, + roadChains: [], + riverChains: [], + roadColor: "#a16207", + riverColor: "#3b82f6", + hexOrientation: "flat", + tablesFolder: "world/tables", + factionsFolder: "", + defaultTableDice: 100, + hexEditorTerrainCollapsed: false, + hexEditorFeaturesCollapsed: false, + hexEditorNotesCollapsed: false, + rollTableExcludedFolders: ["terrain"], + encounterTableExcludedFolders: ["terrain"], +}; diff --git a/CLAUDE.md b/CLAUDE.md index 0b32d11..d82e38d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,9 +28,9 @@ Slash commands (invoke inside Claude Code): Each coding session: 1. Run `npm run dev` in a terminal (or `/dev` from Claude Code) — esbuild watches for changes and rebuilds `main.js` on every save -2. After a rebuild, reload the plugin in Obsidian via the developer console (Ctrl+Shift+I): - ```js - app.plugins.disablePlugin('duckmage-plugin'); app.plugins.enablePlugin('duckmage-plugin'); +2. After a rebuild, reload the plugin in Obsidian: + ```bash + powershell.exe -Command "obsidian plugin:reload id=duckmage-plugin" ``` 3. Use `/rebuild` for a final production build before committing (runs the TypeScript type-check and tests too) diff --git a/src/constants.ts b/src/constants.ts index 86d8716..229c273 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -8,64 +8,266 @@ export const VIEW_TYPE_RANDOM_TABLES = "duckmage-random-tables"; export const DEFAULT_TERRAIN_PALETTE: TerrainColor[] = [ // Sea - { name: "trench", color: "#14223d", category: "sea" }, - { name: "ocean", color: "#29507f", category: "sea" }, + { name: "trench", color: "#14223d", category: "sea" }, + { name: "ocean", color: "#29507f", category: "sea" }, { name: "shallows", color: "#4a82a5", category: "sea" }, // Uncategorised - { name: "water", color: "#60a5fa" }, - { name: "urban", color: "#888888" }, + { name: "water", color: "#60a5fa" }, + { name: "urban", color: "#888888" }, // Lowlands - { name: "grass", color: "#69a168", icon: "bw-grassland.png", category: "lowlands" }, - { name: "hills", color: "#e0e8a1", icon: "bw-hills.png", category: "lowlands" }, - { name: "foothills", color: "#81c191", icon: "bw-hills.png", category: "lowlands" }, + { + name: "grass", + color: "#69a168", + icon: "bw-grassland.png", + category: "lowlands", + }, + { + name: "hills", + color: "#e0e8a1", + icon: "bw-hills.png", + category: "lowlands", + }, + { + name: "foothills", + color: "#81c191", + icon: "bw-hills.png", + category: "lowlands", + }, // Snow - { name: "snow", color: "#e0f2fe", category: "snow" }, - { name: "mountains snow", color: "#bfdbfe", icon: "bw-mountains-snow.png", category: "snow" }, + { name: "snow", color: "#e0f2fe", category: "snow" }, + { + name: "mountains snow", + color: "#bfdbfe", + icon: "bw-mountains-snow.png", + category: "snow", + }, // Desert - { name: "desert", color: "#ecdba2", category: "desert" }, - { name: "desert rocky", color: "#e6bc60", icon: "bw-desert-rocky.png", category: "desert" }, - { name: "dunes", color: "#eccd7e", icon: "bw-dunes.png", category: "desert" }, - { name: "cactus", color: "#e2b75a", icon: "bw-cactus.png", category: "desert" }, - { name: "cactus heavy", color: "#ddb869", icon: "bw-cactus-heavy.png", category: "desert" }, - { name: "badlands", color: "#c2410c", icon: "bw-badlands.png", category: "desert" }, - { name: "brokenlands", color: "#92400e", icon: "bw-brokenlands.png", category: "desert" }, + { name: "desert", color: "#ecdba2", category: "desert" }, + { + name: "desert rocky", + color: "#e6bc60", + icon: "bw-desert-rocky.png", + category: "desert", + }, + { name: "dunes", color: "#eccd7e", icon: "bw-dunes.png", category: "desert" }, + { + name: "cactus", + color: "#e2b75a", + icon: "bw-cactus.png", + category: "desert", + }, + { + name: "cactus heavy", + color: "#ddb869", + icon: "bw-cactus-heavy.png", + category: "desert", + }, + { + name: "badlands", + color: "#c2410c", + icon: "bw-badlands.png", + category: "desert", + }, + { + name: "brokenlands", + color: "#92400e", + icon: "bw-brokenlands.png", + category: "desert", + }, // Forest - { name: "forest", color: "#2d9553", icon: "bw-forest.png", category: "forest" }, - { name: "forest heavy", color: "#15803d", icon: "bw-forest-heavy.png", category: "forest" }, - { name: "forested hills", color: "#22c55e", icon: "bw-forested-hills.png", category: "forest" }, - { name: "forested mountain", color: "#6b9e7c", icon: "bw-forested-mountain.png", category: "forest" }, - { name: "forested mountains", color: "#466d46", icon: "bw-forested-mountains.png", iconColor: "#1b1d1c", category: "forest" }, - { name: "mixed forest", color: "#16a34a", icon: "bw-forest-mixed.png", category: "forest" }, - { name: "mixed forest heavy", color: "#15803d", icon: "bw-forest-mixed-heavy.png", category: "forest" }, - { name: "mixed forest hills", color: "#22c55e", icon: "bw-forest-mixed-hills.png", category: "forest" }, - { name: "mixed forest mountain", color: "#6b9e7c", icon: "bw-forest-mixed-mountain.png", category: "forest" }, - { name: "mixed forest mountains", color: "#5e8c6a", icon: "bw-forest-mixed-mountains.png", category: "forest" }, + { + name: "forest", + color: "#2d9553", + icon: "bw-forest.png", + category: "forest", + }, + { + name: "forest heavy", + color: "#15803d", + icon: "bw-forest-heavy.png", + category: "forest", + }, + { + name: "forested hills", + color: "#22c55e", + icon: "bw-forested-hills.png", + category: "forest", + }, + { + name: "forested mountain", + color: "#6b9e7c", + icon: "bw-forested-mountain.png", + category: "forest", + }, + { + name: "forested mountains", + color: "#466d46", + icon: "bw-forested-mountains.png", + iconColor: "#1b1d1c", + category: "forest", + }, + { + name: "mixed forest", + color: "#16a34a", + icon: "bw-forest-mixed.png", + category: "forest", + }, + { + name: "mixed forest heavy", + color: "#15803d", + icon: "bw-forest-mixed-heavy.png", + category: "forest", + }, + { + name: "mixed forest hills", + color: "#22c55e", + icon: "bw-forest-mixed-hills.png", + category: "forest", + }, + { + name: "mixed forest mountain", + color: "#6b9e7c", + icon: "bw-forest-mixed-mountain.png", + category: "forest", + }, + { + name: "mixed forest mountains", + color: "#5e8c6a", + icon: "bw-forest-mixed-mountains.png", + category: "forest", + }, // Darkwood (evergreen) - { name: "evergreen", color: "#428a5e", icon: "bw-evergreen.png", category: "darkwood" }, - { name: "evergreen heavy", color: "#257445", icon: "bw-evergreen-heavy.png", iconColor: "#1f1e1e", category: "darkwood" }, - { name: "evergreen hills", color: "#328651", icon: "bw-evergreen-hills.png", iconColor: "#292929", category: "darkwood" }, - { name: "evergreen mountain", color: "#6b806b", icon: "bw-evergreen-mountain.png", category: "darkwood" }, - { name: "evergreen mountains", color: "#8c9d80", icon: "bw-evergreen-mountains.png", category: "darkwood" }, + { + name: "evergreen", + color: "#428a5e", + icon: "bw-evergreen.png", + category: "darkwood", + }, + { + name: "evergreen heavy", + color: "#257445", + icon: "bw-evergreen-heavy.png", + iconColor: "#1f1e1e", + category: "darkwood", + }, + { + name: "evergreen hills", + color: "#328651", + icon: "bw-evergreen-hills.png", + iconColor: "#292929", + category: "darkwood", + }, + { + name: "evergreen mountain", + color: "#6b806b", + icon: "bw-evergreen-mountain.png", + category: "darkwood", + }, + { + name: "evergreen mountains", + color: "#8c9d80", + icon: "bw-evergreen-mountains.png", + category: "darkwood", + }, // Island (jungle / volcanic) - { name: "jungle", color: "#15803d", icon: "bw-jungle.png", category: "island" }, - { name: "jungle heavy", color: "#14532d", icon: "bw-jungle-heavy.png", iconColor: "#ffffff", category: "island" }, - { name: "jungle hills", color: "#4ade80", icon: "bw-jungle-hills.png", category: "island" }, - { name: "jungle mountain", color: "#4d7c0f", icon: "bw-jungle-mountain.png", category: "island" }, - { name: "jungle mountains", color: "#3f6212", icon: "bw-jungle-mountains.png", category: "island" }, - { name: "volcano", color: "#b91c1c", icon: "bw-volcano.png", category: "island" }, - { name: "volcano dormant", color: "#78350f", icon: "bw-volcano-dormant.png", iconColor: "#ffffff", category: "island" }, + { + name: "jungle", + color: "#15803d", + icon: "bw-jungle.png", + category: "island", + }, + { + name: "jungle heavy", + color: "#14532d", + icon: "bw-jungle-heavy.png", + iconColor: "#ffffff", + category: "island", + }, + { + name: "jungle hills", + color: "#4ade80", + icon: "bw-jungle-hills.png", + category: "island", + }, + { + name: "jungle mountain", + color: "#4d7c0f", + icon: "bw-jungle-mountain.png", + category: "island", + }, + { + name: "jungle mountains", + color: "#3f6212", + icon: "bw-jungle-mountains.png", + category: "island", + }, + { + name: "volcano", + color: "#b91c1c", + icon: "bw-volcano.png", + category: "island", + }, + { + name: "volcano dormant", + color: "#78350f", + icon: "bw-volcano-dormant.png", + iconColor: "#ffffff", + category: "island", + }, // Mountain - { name: "cliffs", color: "#a86f1f", icon: "bw-brokenlands.png", category: "mountain" }, - { name: "mountain", color: "#a77649", icon: "bw-mountain.png", category: "mountain" }, - { name: "Mountain Ridge", color: "#c55f0d", icon: "bw-mountains.png", category: "mountain" }, - { name: "peak", color: "#78716c", icon: "bw-mountain.png", category: "mountain" }, + { + name: "cliffs", + color: "#a86f1f", + icon: "bw-brokenlands.png", + category: "mountain", + }, + { + name: "mountain", + color: "#a77649", + icon: "bw-mountain.png", + category: "mountain", + }, + { + name: "mountain ridge", + color: "#c55f0d", + icon: "bw-mountains.png", + category: "mountain", + }, + { + name: "peak", + color: "#78716c", + icon: "bw-mountain.png", + category: "mountain", + }, // Bog (wetlands) - { name: "marsh", color: "#909f23", icon: "bw-marsh.png", category: "bog" }, - { name: "swamp", color: "#4e5214", icon: "bw-swamp.png", iconColor: "#f9fbf9", category: "bog" }, - { name: "bog", color: "#432e6b", icon: "bw-swamp.png", iconColor: "#ffffff", category: "bog" }, + { name: "marsh", color: "#909f23", icon: "bw-marsh.png", category: "bog" }, + { + name: "swamp", + color: "#4e5214", + icon: "bw-swamp.png", + iconColor: "#f9fbf9", + category: "bog", + }, + { + name: "bog", + color: "#432e6b", + icon: "bw-swamp.png", + iconColor: "#ffffff", + category: "bog", + }, // Coast - { name: "beach", color: "#cac181", icon: "bw-grassland.png", category: "coast" }, - { name: "Salt Flats", color: "#f7eaba", icon: "bw-dunes.png", category: "coast" }, + { + name: "beach", + color: "#cac181", + icon: "bw-grassland.png", + category: "coast", + }, + { + name: "salt flats", + color: "#f7eaba", + icon: "bw-dunes.png", + category: "coast", + }, ]; export const DEFAULT_SETTINGS: DuckmagePluginSettings = { @@ -79,9 +281,18 @@ export const DEFAULT_SETTINGS: DuckmagePluginSettings = { iconsFolder: "", templatePath: "", hexGap: "0.15", - terrainPalettes: [{ name: DEFAULT_PALETTE_NAME, terrains: DEFAULT_TERRAIN_PALETTE }], + terrainPalettes: [ + { name: DEFAULT_PALETTE_NAME, terrains: DEFAULT_TERRAIN_PALETTE }, + ], regions: [ - { name: "default", paletteName: DEFAULT_PALETTE_NAME, gridSize: { cols: 20, rows: 16 }, gridOffset: { x: 0, y: 0 }, roadChains: [], riverChains: [] }, + { + name: "default", + paletteName: DEFAULT_PALETTE_NAME, + gridSize: { cols: 20, rows: 16 }, + gridOffset: { x: 0, y: 0 }, + roadChains: [], + riverChains: [], + }, ], zoomLevel: 1, roadColor: "#a16207", diff --git a/src/hex-map/HexMapView.ts b/src/hex-map/HexMapView.ts index 2d8830c..fb83c48 100644 --- a/src/hex-map/HexMapView.ts +++ b/src/hex-map/HexMapView.ts @@ -82,6 +82,13 @@ export class HexMapView extends ItemView { >(); private flushing = new Set(); // "t:" or "i:" private savingIndicatorEl: HTMLElement | null = null; + // Terrain undo + private readonly TERRAIN_UNDO_DEPTH = 20; + private terrainUndoStack: Array<{ x: number; y: number; path: string; oldTerrain: string | null; newTerrain: string | null }[]> = []; + private terrainRedoStack: Array<{ x: number; y: number; path: string; oldTerrain: string | null; newTerrain: string | null }[]> = []; + private currentTerrainStroke: Map | null = null; + private undoBtn: HTMLButtonElement | null = null; + private redoBtn: HTMLButtonElement | null = null; activeRegionName = "default"; private regionBtn: HTMLButtonElement | null = null; @@ -174,6 +181,7 @@ export class HexMapView extends ItemView { if (this.drawingMode === "terrain" || this.drawingMode === "icon") { isTerrainPainting = true; lastPaintedKey = null; + if (this.drawingMode === "terrain") this.currentTerrainStroke = new Map(); // Paint the hex under the cursor immediately const hexEl = (e.target as HTMLElement).closest( ".duckmage-hex", @@ -248,6 +256,7 @@ export class HexMapView extends ItemView { }); this.registerDomEvent(document, "mouseup", () => { + if (isTerrainPainting && this.drawingMode === "terrain") this.commitTerrainStroke(); isTerrainPainting = false; lastPaintedKey = null; isDragging = false; @@ -366,12 +375,31 @@ export class HexMapView extends ItemView { this.updateRegionBtnLabel(); this.regionBtn.addEventListener("click", () => new RegionModal(this.app, this.plugin, this, () => { + this.terrainUndoStack = []; + this.terrainRedoStack = []; + this.updateUndoButton(); this.updateRegionBtnLabel(); (this.leaf as any).updateHeader(); this.renderGrid(); }).open(), ); + this.undoBtn = controlsEl.createEl("button", { + cls: "duckmage-undo-btn-map", + text: "↩", + attr: { title: "Undo last terrain stroke (up to 20)" }, + }); + this.undoBtn.disabled = true; + this.undoBtn.addEventListener("click", () => this.undoLastTerrainStroke()); + + this.redoBtn = controlsEl.createEl("button", { + cls: "duckmage-undo-btn-map duckmage-redo-btn-map", + text: "↪", + attr: { title: "Redo last undone terrain stroke" }, + }); + this.redoBtn.disabled = true; + this.redoBtn.addEventListener("click", () => this.redoLastTerrainStroke()); + const helpBtn = controlsEl.createEl("button", { cls: "duckmage-help-btn", title: "Controls & tools", @@ -577,6 +605,7 @@ export class HexMapView extends ItemView { private exitTerrainMode(): void { if (this.drawingMode !== "terrain") return; + this.commitTerrainStroke(); this.drawingMode = null; this.paintTerrainName = null; this.terrainPickMode = false; @@ -1259,6 +1288,14 @@ export class HexMapView extends ItemView { // ── Queue background file write (coalescing per-hex) ──────────────── const path = this.plugin.hexPath(hx, hy, this.activeRegionName); + if (this.currentTerrainStroke) { + if (!this.currentTerrainStroke.has(path)) { + const oldTerrain = this.app.metadataCache.getCache(path)?.frontmatter?.terrain ?? null; + this.currentTerrainStroke.set(path, { x: hx, y: hy, path, oldTerrain, newTerrain: terrain }); + } else { + this.currentTerrainStroke.get(path)!.newTerrain = terrain; + } + } this.scheduleTerrainWrite(hx, hy, path, terrain); } } @@ -1380,6 +1417,81 @@ export class HexMapView extends ItemView { // writes: the in-flight one and then the final value. No writes are lost; no // stale intermediate value can overwrite a newer one. + private applyTerrainToHexEl(hexEl: HTMLElement, terrain: string | null): void { + const palette = this.plugin.getRegionPalette(this.activeRegionName); + const entry = terrain != null ? palette.find((p) => p.name === terrain) : undefined; + hexEl.style.backgroundColor = entry?.color ?? ""; + hexEl.querySelector(".duckmage-hex-icon")?.remove(); + hexEl.querySelector(".duckmage-hex-dot")?.remove(); + if (entry?.icon) { + try { + const iconEl = createIconEl(hexEl, getIconUrl(this.plugin, entry.icon), entry.name, entry.iconColor, "duckmage-hex-icon"); + hexEl.insertBefore(iconEl, hexEl.querySelector(".duckmage-hex-label")); + } catch (err) { + console.warn(`[hexmaker] failed to render icon for terrain "${terrain}":`, err); + } + } + if (terrain !== null) hexEl.addClass("duckmage-hex-exists"); + else hexEl.removeClass("duckmage-hex-exists"); + } + + private commitTerrainStroke(): void { + if (!this.currentTerrainStroke || this.currentTerrainStroke.size === 0) { + this.currentTerrainStroke = null; + return; + } + const entries = [...this.currentTerrainStroke.values()]; + this.terrainUndoStack.push(entries); + if (this.terrainUndoStack.length > this.TERRAIN_UNDO_DEPTH) + this.terrainUndoStack.shift(); + this.terrainRedoStack = []; // new paint invalidates redo history + this.currentTerrainStroke = null; + this.updateUndoButton(); + } + + private undoLastTerrainStroke(): void { + const stroke = this.terrainUndoStack.pop(); + if (!stroke) return; + this.terrainRedoStack.push(stroke); + this.applyStroke(stroke, "old"); + this.updateUndoButton(); + } + + private redoLastTerrainStroke(): void { + const stroke = this.terrainRedoStack.pop(); + if (!stroke) return; + this.terrainUndoStack.push(stroke); + this.applyStroke(stroke, "new"); + this.updateUndoButton(); + } + + private applyStroke( + stroke: { x: number; y: number; path: string; oldTerrain: string | null; newTerrain: string | null }[], + which: "old" | "new", + ): void { + for (const entry of stroke) { + const terrain = which === "old" ? entry.oldTerrain : entry.newTerrain; + try { + const hexEl = this.viewportEl?.querySelector( + `[data-x="${entry.x}"][data-y="${entry.y}"]`, + ); + if (hexEl) this.applyTerrainToHexEl(hexEl, terrain); + } catch (err) { + console.warn(`[hexmaker] stroke visual update failed for ${entry.path}:`, err); + } + try { + this.scheduleTerrainWrite(entry.x, entry.y, entry.path, terrain); + } catch (err) { + console.warn(`[hexmaker] stroke write scheduling failed for ${entry.path}:`, err); + } + } + } + + private updateUndoButton(): void { + if (this.undoBtn) this.undoBtn.disabled = this.terrainUndoStack.length === 0; + if (this.redoBtn) this.redoBtn.disabled = this.terrainRedoStack.length === 0; + } + private updateSavingIndicator(): void { const count = this.pendingTerrainWrites.size + diff --git a/styles.css b/styles.css index 8e3968f..fe48fcf 100644 --- a/styles.css +++ b/styles.css @@ -147,7 +147,7 @@ content: ""; position: absolute; inset: 0; - background: rgba(255, 190, 0, 0.55); + background: rgba(255, 210, 0, 0.3); pointer-events: none; } @@ -1008,7 +1008,8 @@ div.duckmage-terrain-preview-icon { } .duckmage-table-btn, -.duckmage-rt-btn { +.duckmage-rt-btn, +.duckmage-undo-btn-map { position: absolute; top: 8px; z-index: 10; @@ -1035,8 +1036,22 @@ div.duckmage-terrain-preview-icon { left: 44px; } +.duckmage-undo-btn-map { + left: 8px; + top: 42px; +} +.duckmage-redo-btn-map { + left: 44px; + top: 42px; +} +.duckmage-undo-btn-map:disabled { + opacity: 0.3; + cursor: default; +} + .duckmage-table-btn:hover, -.duckmage-rt-btn:hover { +.duckmage-rt-btn:hover, +.duckmage-undo-btn-map:not(:disabled):hover { opacity: 1; color: var(--text-normal); }