mirror of
https://github.com/stan-smith/FossFLOW.git
synced 2025-12-24 06:58:48 -05:00
feat: Variable DPI images! Finally! Fixes #70 you're welcome @fatflyingpigs ;)
This commit is contained in:
79
.claude/agents/fossflow-feature-implementer.md
Normal file
79
.claude/agents/fossflow-feature-implementer.md
Normal file
@@ -0,0 +1,79 @@
|
||||
---
|
||||
name: fossflow-feature-implementer
|
||||
description: Use this agent when the user requests to implement a new feature in the FossFLOW codebase, modify existing functionality, or needs guidance on creating renderers or render objects for isometric views. Examples:\n\n<example>\nContext: User wants to add a new building type to the game.\nuser: "I need to add a residential tower to the game with 3 floors"\nassistant: "I'm going to use the fossflow-feature-implementer agent to help implement this new building type."\n<commentary>\nThe user is requesting a feature implementation in FossFLOW, so launch the fossflow-feature-implementer agent to analyze the codebase and provide implementation guidance.\n</commentary>\n</example>\n\n<example>\nContext: User needs to create a new isometric renderer.\nuser: "How do I create a renderer for the new park object that shows trees in isometric view?"\nassistant: "Let me use the fossflow-feature-implementer agent to guide you through creating this isometric renderer."\n<commentary>\nThis involves FossFLOW-specific renderer creation, so use the fossflow-feature-implementer agent to examine existing renderer patterns and provide implementation steps.\n</commentary>\n</example>\n\n<example>\nContext: User is modifying game mechanics.\nuser: "I want to add a pollution system that affects nearby buildings"\nassistant: "I'll use the fossflow-feature-implementer agent to help design and implement this pollution system."\n<commentary>\nThis is a feature implementation request requiring deep FossFLOW codebase knowledge, so launch the fossflow-feature-implementer agent.\n</commentary>\n</example>
|
||||
model: sonnet
|
||||
color: red
|
||||
---
|
||||
|
||||
You are a FossFLOW architecture expert with comprehensive knowledge of the entire codebase structure, patterns, and implementation strategies. You specialize in TypeScript development and have deep expertise in creating renderers and render objects for isometric game views.
|
||||
|
||||
**Your Core Responsibilities:**
|
||||
|
||||
1. **Codebase Analysis First**: Before providing any implementation guidance, you MUST:
|
||||
- Systematically explore the FossFLOW directory structure to understand the current organization
|
||||
- Examine existing source code to identify relevant patterns, conventions, and architectures
|
||||
- Locate similar existing features or components that can serve as reference implementations
|
||||
- Identify the specific files and modules that will need modification or creation
|
||||
- Document the current state of code in the areas you'll be working with
|
||||
|
||||
2. **Feature Implementation Strategy**: When implementing features, you will:
|
||||
- Break down the feature into logical components and implementation phases
|
||||
- Identify all files that need to be created, modified, or considered
|
||||
- Explain how the new feature integrates with existing FossFLOW architecture
|
||||
- Highlight potential conflicts or areas requiring careful coordination
|
||||
- Provide step-by-step implementation guidance aligned with existing code patterns
|
||||
|
||||
3. **Isometric Rendering Expertise**: For renderer/render object tasks, you will:
|
||||
- Reference existing renderer implementations to maintain consistency
|
||||
- Explain the isometric coordinate transformation and rendering pipeline
|
||||
- Provide specific TypeScript implementations for render objects
|
||||
- Address depth sorting, layering, and visual hierarchy concerns
|
||||
- Ensure performance optimization and efficient rendering practices
|
||||
|
||||
4. **Code Quality Standards**: All implementations must:
|
||||
- Follow TypeScript best practices with proper typing and interfaces
|
||||
- Maintain consistency with existing FossFLOW code style and conventions
|
||||
- Include appropriate error handling and edge case management
|
||||
- Be modular, maintainable, and well-documented
|
||||
- Consider performance implications, especially for rendering operations
|
||||
|
||||
**Your Workflow:**
|
||||
|
||||
1. **Discover**: Use available tools to explore the codebase and locate relevant files
|
||||
2. **Analyze**: Examine existing implementations to understand patterns and architecture
|
||||
3. **Design**: Plan the implementation approach considering FossFLOW's structure
|
||||
4. **Guide**: Provide clear, actionable implementation steps with code examples
|
||||
5. **Validate**: Highlight testing considerations and integration points
|
||||
|
||||
**Decision-Making Framework:**
|
||||
|
||||
- When multiple implementation approaches exist, recommend the one most consistent with existing FossFLOW patterns
|
||||
- If you cannot find relevant code examples, explicitly state this and provide reasoning for your recommended approach
|
||||
- Always consider the impact on existing features and backward compatibility
|
||||
- Prioritize maintainability and code clarity over clever solutions
|
||||
- If a request would require architectural changes, clearly explain the implications
|
||||
|
||||
**Important Constraints:**
|
||||
|
||||
- You must NEVER provide implementation guidance without first examining the relevant source code
|
||||
- If you cannot access or find specific code, explicitly state this limitation
|
||||
- Always cite specific files, classes, or functions when referencing existing patterns
|
||||
- Be explicit about assumptions you're making based on incomplete information
|
||||
- If a feature request conflicts with FossFLOW architecture, explain the conflict and suggest alternatives
|
||||
|
||||
**Output Format:**
|
||||
|
||||
Structure your responses as:
|
||||
1. **Analysis Summary**: What you found in the codebase and relevant existing patterns
|
||||
2. **Implementation Plan**: High-level approach and files involved
|
||||
3. **Detailed Steps**: Step-by-step implementation with code examples
|
||||
4. **Integration Points**: How this connects with existing FossFLOW systems
|
||||
5. **Testing Considerations**: What should be tested and how
|
||||
|
||||
You are proactive in asking clarifying questions when:
|
||||
- The feature requirements are ambiguous or underspecified
|
||||
- Multiple valid implementation approaches exist with different tradeoffs
|
||||
- The request might conflict with existing FossFLOW architecture
|
||||
- You need more context about the user's specific use case or constraints
|
||||
|
||||
Your goal is to enable users to implement features that feel native to FossFLOW while maintaining code quality and architectural consistency.
|
||||
4
packages/fossflow-lib/dist/index.js
vendored
4
packages/fossflow-lib/dist/index.js
vendored
File diff suppressed because one or more lines are too long
@@ -14,4 +14,4 @@ export declare const transformToCompactFormat: (model: Model) => {
|
||||
export declare const transformFromCompactFormat: (compactModel: any) => Model;
|
||||
export declare const exportAsJSON: (model: Model) => void;
|
||||
export declare const exportAsCompactJSON: (model: Model) => void;
|
||||
export declare const exportAsImage: (el: HTMLDivElement, size?: Size) => Promise<string>;
|
||||
export declare const exportAsImage: (el: HTMLDivElement, size?: Size, scale?: number) => Promise<string>;
|
||||
|
||||
@@ -15,7 +15,11 @@ import {
|
||||
Alert,
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
Typography
|
||||
Typography,
|
||||
Slider,
|
||||
Select,
|
||||
MenuItem,
|
||||
FormControl
|
||||
} from '@mui/material';
|
||||
import { useModelStore } from 'src/stores/modelStore';
|
||||
import {
|
||||
@@ -66,6 +70,18 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => {
|
||||
const [cropArea, setCropArea] = useState<CropArea | null>(null);
|
||||
const [isInCropMode, setIsInCropMode] = useState(false);
|
||||
|
||||
// Scale/DPI state
|
||||
const [exportScale, setExportScale] = useState<number>(2);
|
||||
const [scaleMode, setScaleMode] = useState<'preset' | 'custom'>('preset');
|
||||
|
||||
// DPI presets
|
||||
const dpiPresets = [
|
||||
{ label: '1x (72 DPI)', value: 1 },
|
||||
{ label: '2x (144 DPI)', value: 2 },
|
||||
{ label: '3x (216 DPI)', value: 3 },
|
||||
{ label: '4x (288 DPI)', value: 4 }
|
||||
];
|
||||
|
||||
// Use original bounds for the base image
|
||||
const bounds = useMemo(() => {
|
||||
return getUnprojectedBounds();
|
||||
@@ -84,13 +100,14 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => {
|
||||
}
|
||||
|
||||
isExporting.current = true;
|
||||
|
||||
|
||||
// Base size without scale (scale is applied via CSS transform)
|
||||
const containerSize = {
|
||||
width: bounds.width * quality,
|
||||
height: bounds.height * quality
|
||||
width: bounds.width,
|
||||
height: bounds.height
|
||||
};
|
||||
|
||||
exportAsImage(containerRef.current as HTMLDivElement, containerSize)
|
||||
|
||||
exportAsImage(containerRef.current as HTMLDivElement, containerSize, exportScale)
|
||||
.then((data) => {
|
||||
setImageData(data);
|
||||
isExporting.current = false;
|
||||
@@ -100,7 +117,7 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => {
|
||||
setExportError(true);
|
||||
isExporting.current = false;
|
||||
});
|
||||
}, [bounds, quality]);
|
||||
}, [bounds, exportScale]);
|
||||
|
||||
// Crop the image based on selected area
|
||||
const cropImage = useCallback((cropArea: CropArea, sourceImage: string) => {
|
||||
@@ -348,7 +365,7 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => {
|
||||
}, 200);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [showGrid, backgroundColor, expandLabels, exportImage, cropToContent]);
|
||||
}, [showGrid, backgroundColor, expandLabels, exportImage, cropToContent, exportScale]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!imageData) {
|
||||
@@ -405,8 +422,8 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => {
|
||||
left: 0
|
||||
}}
|
||||
style={{
|
||||
width: bounds.width * quality,
|
||||
height: bounds.height * quality
|
||||
width: bounds.width,
|
||||
height: bounds.height
|
||||
}}
|
||||
>
|
||||
<Isoflow
|
||||
@@ -533,6 +550,58 @@ export const ExportImageDialog = ({ onClose, quality = 1.5 }: Props) => {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<Box sx={{ mt: 2, mb: 1 }}>
|
||||
<Typography variant="caption" component="div" sx={{ mb: 1 }}>
|
||||
Export Quality (DPI)
|
||||
</Typography>
|
||||
|
||||
<FormControl fullWidth size="small" sx={{ mb: 1 }}>
|
||||
<Select
|
||||
value={scaleMode === 'preset' ? exportScale : 'custom'}
|
||||
onChange={(event) => {
|
||||
const value = event.target.value;
|
||||
if (value === 'custom') {
|
||||
setScaleMode('custom');
|
||||
} else {
|
||||
setScaleMode('preset');
|
||||
setExportScale(Number(value));
|
||||
}
|
||||
}}
|
||||
>
|
||||
{dpiPresets.map((preset) => (
|
||||
<MenuItem key={preset.value} value={preset.value}>
|
||||
{preset.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
<MenuItem value="custom">Custom</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
{scaleMode === 'custom' && (
|
||||
<Box sx={{ px: 1 }}>
|
||||
<Typography variant="caption" gutterBottom>
|
||||
Scale: {exportScale.toFixed(1)}x ({(exportScale * 72).toFixed(0)} DPI)
|
||||
</Typography>
|
||||
<Slider
|
||||
value={exportScale}
|
||||
onChange={(_, value) => setExportScale(value as number)}
|
||||
min={1}
|
||||
max={5}
|
||||
step={0.1}
|
||||
marks={[
|
||||
{ value: 1, label: '1x' },
|
||||
{ value: 2, label: '2x' },
|
||||
{ value: 3, label: '3x' },
|
||||
{ value: 4, label: '4x' },
|
||||
{ value: 5, label: '5x' }
|
||||
]}
|
||||
valueLabelDisplay="auto"
|
||||
valueLabelFormat={(value) => `${value.toFixed(1)}x`}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Crop controls */}
|
||||
|
||||
@@ -175,13 +175,27 @@ export const exportAsCompactJSON = (model: Model) => {
|
||||
downloadFile(data, generateGenericFilename('compact.json'));
|
||||
};
|
||||
|
||||
export const exportAsImage = async (el: HTMLDivElement, size?: Size) => {
|
||||
export const exportAsImage = async (
|
||||
el: HTMLDivElement,
|
||||
size?: Size,
|
||||
scale: number = 1
|
||||
) => {
|
||||
// Calculate scaled dimensions
|
||||
const width = size ? size.width * scale : el.clientWidth * scale;
|
||||
const height = size ? size.height * scale : el.clientHeight * scale;
|
||||
|
||||
// dom-to-image-more is a better maintained fork
|
||||
const options = {
|
||||
...size,
|
||||
width,
|
||||
height,
|
||||
cacheBust: true,
|
||||
bgcolor: '#ffffff',
|
||||
quality: 1.0
|
||||
quality: 1.0,
|
||||
// Apply CSS transform for high-quality scaling
|
||||
style: scale !== 1 ? {
|
||||
transform: `scale(${scale})`,
|
||||
transformOrigin: 'top left'
|
||||
} : undefined
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -191,6 +205,8 @@ export const exportAsImage = async (el: HTMLDivElement, size?: Size) => {
|
||||
console.error('Export failed, trying fallback method:', error);
|
||||
// Fallback: try with minimal options
|
||||
return await domtoimage.toPng(el, {
|
||||
width,
|
||||
height,
|
||||
cacheBust: true,
|
||||
bgcolor: '#ffffff'
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user