mirror of
https://github.com/sbuffkin/hexmaker.git
synced 2026-07-22 14:30:24 +00:00
add undo and redo
This commit is contained in:
parent
cbe1b01823
commit
632e514c3e
6 changed files with 524 additions and 186 deletions
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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"],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
309
src/constants.ts
309
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",
|
||||
|
|
|
|||
|
|
@ -82,6 +82,13 @@ export class HexMapView extends ItemView {
|
|||
>();
|
||||
private flushing = new Set<string>(); // "t:<path>" or "i:<path>"
|
||||
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<string, { x: number; y: number; path: string; oldTerrain: string | null; newTerrain: string | null }> | 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<HTMLElement>(
|
||||
".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<HTMLElement>(
|
||||
`[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 +
|
||||
|
|
|
|||
21
styles.css
21
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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue