mirror of
https://github.com/stan-smith/FossFLOW.git
synced 2025-12-23 22:48:57 -05:00
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
import { Stack, Alert, IconButton as MUIIconButton, Box, Button, FormControlLabel, Checkbox, Typography } from '@mui/material';
|
||||
import { Stack, Alert, IconButton as MUIIconButton, Box, Button, FormControlLabel, Checkbox, Typography, Slider } from '@mui/material';
|
||||
import { ControlsContainer } from 'src/components/ItemControls/components/ControlsContainer';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { useModelStore } from 'src/stores/modelStore';
|
||||
@@ -27,6 +27,7 @@ export const IconSelectionControls = () => {
|
||||
const { iconCategories } = useIconCategories();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [treatAsIsometric, setTreatAsIsometric] = useState(true);
|
||||
const [iconScale, setIconScale] = useState(100);
|
||||
const [showAlert, setShowAlert] = useState(() => {
|
||||
// Check localStorage to see if user has dismissed the alert
|
||||
return localStorage.getItem('fossflow-show-drag-hint') !== 'false';
|
||||
@@ -111,10 +112,11 @@ export const IconSelectionControls = () => {
|
||||
const TARGET_SIZE = 128; // Square size for consistency
|
||||
|
||||
// Calculate scaling to fit within square while maintaining aspect ratio
|
||||
// Remove the upper limit (1) to allow upscaling of small images
|
||||
const scale = Math.min(TARGET_SIZE / img.width, TARGET_SIZE / img.height);
|
||||
const scaledWidth = img.width * scale;
|
||||
const scaledHeight = img.height * scale;
|
||||
const basScale = Math.min(TARGET_SIZE / img.width, TARGET_SIZE / img.height);
|
||||
// Apply user's custom scaling
|
||||
const finalScale = basScale * (iconScale / 100);
|
||||
const scaledWidth = img.width * finalScale;
|
||||
const scaledHeight = img.height * finalScale;
|
||||
|
||||
// Set canvas to square size
|
||||
canvas.width = TARGET_SIZE;
|
||||
@@ -170,7 +172,7 @@ export const IconSelectionControls = () => {
|
||||
|
||||
// Reset input
|
||||
event.target.value = '';
|
||||
}, [currentIcons, modelActions, iconCategoriesState, uiStateActions, treatAsIsometric]);
|
||||
}, [currentIcons, modelActions, iconCategoriesState, uiStateActions, treatAsIsometric, iconScale]);
|
||||
|
||||
return (
|
||||
<ControlsContainer
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useCallback, useEffect, useRef } from 'react';
|
||||
import { Slider, Box, TextField } from '@mui/material';
|
||||
import { ModelItem, ViewItem } from 'src/types';
|
||||
import { MarkdownEditor } from 'src/components/MarkdownEditor/MarkdownEditor';
|
||||
import { useModelItem } from 'src/hooks/useModelItem';
|
||||
import { useModelStore } from 'src/stores/modelStore';
|
||||
import { DeleteButton } from '../../components/DeleteButton';
|
||||
import { Section } from '../../components/Section';
|
||||
|
||||
@@ -25,6 +26,50 @@ export const NodeSettings = ({
|
||||
onDeleted
|
||||
}: Props) => {
|
||||
const modelItem = useModelItem(node.id);
|
||||
const modelActions = useModelStore((state) => state.actions);
|
||||
const icons = useModelStore((state) => state.icons);
|
||||
|
||||
// Local state for smooth slider interaction
|
||||
const currentIcon = icons.find(icon => icon.id === modelItem?.icon);
|
||||
const [localScale, setLocalScale] = useState(currentIcon?.scale || 1);
|
||||
const debounceRef = useRef<NodeJS.Timeout>();
|
||||
|
||||
// Update local scale when icon changes
|
||||
useEffect(() => {
|
||||
setLocalScale(currentIcon?.scale || 1);
|
||||
}, [currentIcon?.scale]);
|
||||
|
||||
// Debounced update to store
|
||||
const updateIconScale = useCallback((scale: number) => {
|
||||
if (debounceRef.current) {
|
||||
clearTimeout(debounceRef.current);
|
||||
}
|
||||
|
||||
debounceRef.current = setTimeout(() => {
|
||||
const updatedIcons = icons.map(icon =>
|
||||
icon.id === modelItem?.icon
|
||||
? { ...icon, scale }
|
||||
: icon
|
||||
);
|
||||
modelActions.set({ icons: updatedIcons });
|
||||
}, 100); // 100ms debounce
|
||||
}, [icons, modelItem?.icon, modelActions]);
|
||||
|
||||
// Handle slider change with local state + debounced store update
|
||||
const handleScaleChange = useCallback((e: Event, newScale: number | number[]) => {
|
||||
const scale = newScale as number;
|
||||
setLocalScale(scale); // Immediate UI update
|
||||
updateIconScale(scale); // Debounced store update
|
||||
}, [updateIconScale]);
|
||||
|
||||
// Cleanup timeout on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (debounceRef.current) {
|
||||
clearTimeout(debounceRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!modelItem) {
|
||||
return null;
|
||||
@@ -65,6 +110,17 @@ export const NodeSettings = ({
|
||||
/>
|
||||
</Section>
|
||||
)}
|
||||
|
||||
<Section title="Icon size">
|
||||
<Slider
|
||||
marks
|
||||
step={0.1}
|
||||
min={0.3}
|
||||
max={2.5}
|
||||
value={localScale}
|
||||
onChange={handleScaleChange}
|
||||
/>
|
||||
</Section>
|
||||
<Section>
|
||||
<Box>
|
||||
<DeleteButton onClick={onDeleted} />
|
||||
|
||||
@@ -5,10 +5,11 @@ import { useResizeObserver } from 'src/hooks/useResizeObserver';
|
||||
|
||||
interface Props {
|
||||
url: string;
|
||||
scale?: number;
|
||||
onImageLoaded?: () => void;
|
||||
}
|
||||
|
||||
export const IsometricIcon = ({ url, onImageLoaded }: Props) => {
|
||||
export const IsometricIcon = ({ url, scale = 1, onImageLoaded }: Props) => {
|
||||
const ref = useRef();
|
||||
const { size, observe, disconnect } = useResizeObserver();
|
||||
|
||||
@@ -28,7 +29,7 @@ export const IsometricIcon = ({ url, onImageLoaded }: Props) => {
|
||||
src={url}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
width: PROJECTED_TILE_SIZE.width * 0.8,
|
||||
width: PROJECTED_TILE_SIZE.width * 0.8 * scale,
|
||||
top: -size.height,
|
||||
left: -size.width / 2,
|
||||
pointerEvents: 'none'
|
||||
|
||||
@@ -24,7 +24,7 @@ export const NonIsometricIcon = ({ icon }: Props) => {
|
||||
component="img"
|
||||
src={icon.url}
|
||||
alt={`icon-${icon.id}`}
|
||||
sx={{ width: PROJECTED_TILE_SIZE.width * 0.7 }}
|
||||
sx={{ width: PROJECTED_TILE_SIZE.width * 0.7 * (icon.scale || 1) }}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@@ -31,6 +31,7 @@ export const useIcon = (id: string | undefined) => {
|
||||
return (
|
||||
<IsometricIcon
|
||||
url={icon.url}
|
||||
scale={icon.scale || 1}
|
||||
onImageLoaded={() => {
|
||||
setHasLoaded(true);
|
||||
}}
|
||||
|
||||
@@ -6,7 +6,8 @@ export const iconSchema = z.object({
|
||||
name: constrainedStrings.name,
|
||||
url: z.string(),
|
||||
collection: constrainedStrings.name.optional(),
|
||||
isIsometric: z.boolean().optional()
|
||||
isIsometric: z.boolean().optional(),
|
||||
scale: z.number().min(0.1).max(3).optional()
|
||||
});
|
||||
|
||||
export const iconsSchema = z.array(iconSchema);
|
||||
|
||||
Reference in New Issue
Block a user