This commit is contained in:
seb
2026-10-24 06:43:56 +02:00
parent 3801ac4e52
commit eeb4b92a79
4 changed files with 206 additions and 12 deletions

View File

@@ -3,6 +3,8 @@ import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import Switch from '@mui/material/Switch';
import CircularProgress from '@mui/material/CircularProgress';
import GridViewIcon from '@mui/icons-material/GridView';
import DragHandleIcon from '@mui/icons-material/DragHandle';
import AccountTreeIcon from '@mui/icons-material/AccountTree';
import SettingsIcon from '@mui/icons-material/Settings';
@@ -116,7 +118,17 @@ function TopBand({ onSettingsClick, settingsTab, terminalCount, onPlacementClick
/**
* Middle row (1/3): gray band — TextField or terminal toggle.
*/
function MiddleBand({ settingsTab, nameInput, terminals, categoryPosterminalIds, onTerminalToggle, fullHash }) {
function MiddleBand({
settingsTab,
nameInput,
terminals,
categoryPosterminalIds,
onTerminalToggle,
fullHash,
onPhotoUpload,
onPhotoMatrix,
generatingMatrix,
}) {
const isActionTab = settingsTab === -2 || settingsTab === -3 || settingsTab === -4;
const fillSx = {
width: '100%',
@@ -162,22 +174,131 @@ function MiddleBand({ settingsTab, nameInput, terminals, categoryPosterminalIds,
// Show photo upload UI when on photo tab
if (settingsTab === -2) {
if (fullHash) {
return (
<Box sx={fillSx}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<PhotoCameraIcon sx={{ fontSize: 20, color: 'text.secondary' }} />
<Typography
variant="caption"
noWrap
sx={{ maxWidth: 100, color: 'text.secondary' }}
>
Bild löschen
</Typography>
</Box>
</Box>
);
}
return (
<Box sx={fillSx}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<PhotoCameraIcon sx={{ fontSize: 20, color: 'text.secondary' }} />
<Box
sx={{
width: '100%',
height: '100%',
display: 'flex',
bgcolor: 'grey.300',
}}
>
<Box
role="button"
tabIndex={0}
aria-label="Hochladen"
onClick={(e) => {
e.stopPropagation();
onPhotoUpload?.();
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
e.stopPropagation();
onPhotoUpload?.();
}
}}
sx={{
flex: 1,
height: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 0.25,
cursor: 'pointer',
borderRight: '1px solid',
borderColor: 'grey.400',
userSelect: 'none',
transition: 'background-color 0.15s',
'&:hover': { bgcolor: 'grey.400' },
'&:active': { bgcolor: 'grey.500' },
}}
>
<PhotoCameraIcon sx={{ fontSize: 18, color: 'text.secondary' }} />
<Typography
variant="caption"
noWrap
sx={{ maxWidth: 100, color: 'text.secondary' }}
sx={{
fontSize: '0.68rem',
fontWeight: 600,
color: 'text.secondary',
lineHeight: 1,
}}
>
{fullHash ? 'Bild löschen' : 'Hochladen'}
Hochladen
</Typography>
</Box>
<Box
role="button"
tabIndex={0}
aria-label="Matrix"
onClick={(e) => {
e.stopPropagation();
onPhotoMatrix?.();
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
e.stopPropagation();
onPhotoMatrix?.();
}
}}
sx={{
flex: 1,
height: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 0.25,
cursor: generatingMatrix ? 'wait' : 'pointer',
userSelect: 'none',
transition: 'background-color 0.15s',
'&:hover': { bgcolor: generatingMatrix ? undefined : 'grey.400' },
'&:active': { bgcolor: generatingMatrix ? undefined : 'grey.500' },
}}
>
{generatingMatrix ? (
<CircularProgress size={16} sx={{ color: 'text.secondary' }} />
) : (
<GridViewIcon sx={{ fontSize: 18, color: 'text.secondary' }} />
)}
<Typography
variant="caption"
noWrap
sx={{
fontSize: '0.68rem',
fontWeight: 600,
color: 'text.secondary',
lineHeight: 1,
}}
>
Matrix
</Typography>
</Box>
</Box>
);
}
// Show category deletion UI when on delete tab
if (settingsTab === -3) {
return (
@@ -303,7 +424,15 @@ class CategoryBackdrop extends PureComponent {
* lower half = gray band with chevrons + delete
*/
export class CategoryTile extends Component {
state = { settingsTab: -1 }; // -1 = rename, -2 = photo, -3 = delete, -4 = delete recursive, 0..n-1 = terminal index
state = { settingsTab: -1, generatingMatrix: false }; // -1 = rename, -2 = photo, -3 = delete, -4 = delete recursive, 0..n-1 = terminal index
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
}
handleClick = () => {
this.props.onClick?.();
@@ -338,14 +467,27 @@ export class CategoryTile extends Component {
});
};
handlePhotoMatrix = async (e) => {
e?.stopPropagation?.();
if (this.state.generatingMatrix) return;
const fn = this.props.onPhotoMatrix || this.props.onPhotoGenerate;
if (!fn) return;
this.setState({ generatingMatrix: true });
try {
await fn();
} finally {
if (this._isMounted) {
this.setState({ generatingMatrix: false });
}
}
};
handleMiddleBandClick = () => {
const { settingsTab } = this.state;
const { onPhotoUpload, onPhotoDelete, fullHash, onDelete, onDeleteRecursive } = this.props;
const { onPhotoDelete, fullHash, onDelete, onDeleteRecursive } = this.props;
if (settingsTab === -2) {
if (fullHash) {
onPhotoDelete?.();
} else {
onPhotoUpload?.();
}
} else if (settingsTab === -3) {
onDelete?.();
@@ -400,13 +542,15 @@ export class CategoryTile extends Component {
onTerminalToggle,
onPhotoUpload,
onPhotoDelete,
onPhotoMatrix,
onPhotoGenerate,
onPlacementClick,
onOpen,
onDelete,
onDeleteRecursive,
...rest
} = this.props;
const { settingsTab } = this.state;
const { settingsTab, generatingMatrix } = this.state;
// Shared by both modes: the thumbnail backdrop.
const backdrop = (
@@ -431,7 +575,7 @@ export class CategoryTile extends Component {
left: 0,
right: 0,
cursor:
settingsTab === -2 || settingsTab === -3 || settingsTab === -4
(settingsTab === -2 && fullHash) || settingsTab === -3 || settingsTab === -4
? 'pointer'
: 'default',
}}
@@ -443,6 +587,9 @@ export class CategoryTile extends Component {
categoryPosterminalIds={categoryPosterminalIds}
onTerminalToggle={onTerminalToggle}
fullHash={fullHash}
onPhotoUpload={onPhotoUpload}
onPhotoMatrix={this.handlePhotoMatrix}
generatingMatrix={generatingMatrix}
/>
</Box>
{/* Lower third: clickable in edit mode to open/navigate into category */}