Tools + Elements: smaller tiles, display-mode dropdown, dark pill look
- Tiles shrunk (minHeight 46/38/30 per mode, tighter padding, smaller icons) - Add display-mode dropdown (Symbol / Text / Symbol+Text) at top of each panel; choice persists in localStorage under 'dossier_tile_mode' and is shared between both panels - Grid column min-width adapts per mode (icon 40 / text 66 / both 58) - Restore dark pill look: --bg-input background + border-light, full border-radius — tiles read as distinct chips again - Icon-only mode shows a small corner chevron on dropdown tiles (Treppe/Stuetze/Traeger) so the right-click menu stays discoverable
This commit is contained in:
+82
-36
@@ -21,6 +21,32 @@ const labelXs = {
|
|||||||
letterSpacing: '0.06em', textTransform: 'uppercase',
|
letterSpacing: '0.06em', textTransform: 'uppercase',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Anzeige-Modus der Kacheln, geteilt mit Werkzeuge-Panel via localStorage.
|
||||||
|
const TILE_MODE_KEY = 'dossier_tile_mode' // 'both' | 'icon' | 'text'
|
||||||
|
function readTileMode() {
|
||||||
|
try { return localStorage.getItem(TILE_MODE_KEY) || 'both' } catch { return 'both' }
|
||||||
|
}
|
||||||
|
function writeTileMode(m) {
|
||||||
|
try { localStorage.setItem(TILE_MODE_KEY, m) } catch { /* WebView ohne Storage */ }
|
||||||
|
}
|
||||||
|
const TILE_MIN_COL = { icon: 40, text: 66, both: 58 }
|
||||||
|
const TILE_MIN_H = { icon: 38, text: 30, both: 46 }
|
||||||
|
|
||||||
|
function TileModeDropdown({ mode, onChange }) {
|
||||||
|
return (
|
||||||
|
<select
|
||||||
|
value={mode}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
title="Anzeige: Symbol / Text / Symbol + Text"
|
||||||
|
style={{ fontSize: 10, padding: '3px 6px', maxWidth: 130 }}
|
||||||
|
>
|
||||||
|
<option value="icon">Symbol</option>
|
||||||
|
<option value="text">Text</option>
|
||||||
|
<option value="both">Symbol + Text</option>
|
||||||
|
</select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function fmtNum(v) {
|
function fmtNum(v) {
|
||||||
if (v == null || v === '') return ''
|
if (v == null || v === '') return ''
|
||||||
const n = Number(v)
|
const n = Number(v)
|
||||||
@@ -51,8 +77,11 @@ function ReferenzSelector({ value, onChange }) {
|
|||||||
|
|
||||||
// Raster-Kachel — Icon oben, Label darunter, einheitliche Zellgroesse.
|
// Raster-Kachel — Icon oben, Label darunter, einheitliche Zellgroesse.
|
||||||
// hasMenu zeigt ein kleines Chevron neben dem Label (Rechtsklick = Untertypen).
|
// hasMenu zeigt ein kleines Chevron neben dem Label (Rechtsklick = Untertypen).
|
||||||
|
// mode: 'both' (Icon+Text) | 'icon' (nur Symbol) | 'text' (nur Text)
|
||||||
function PillButton({ icon, label, hint, onClick, onContextMenu, disabled,
|
function PillButton({ icon, label, hint, onClick, onContextMenu, disabled,
|
||||||
hasMenu, badge }) {
|
hasMenu, badge, mode = 'both' }) {
|
||||||
|
const showIcon = mode !== 'text'
|
||||||
|
const showLabel = mode !== 'icon'
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
@@ -61,12 +90,12 @@ function PillButton({ icon, label, hint, onClick, onContextMenu, disabled,
|
|||||||
title={hint}
|
title={hint}
|
||||||
style={{
|
style={{
|
||||||
display: 'flex', flexDirection: 'column',
|
display: 'flex', flexDirection: 'column',
|
||||||
alignItems: 'center', justifyContent: 'center', gap: 6,
|
alignItems: 'center', justifyContent: 'center', gap: 4,
|
||||||
padding: '10px 4px',
|
padding: '6px 4px',
|
||||||
minHeight: 56,
|
minHeight: TILE_MIN_H[mode],
|
||||||
background: 'var(--bg-item)',
|
background: 'var(--bg-input)',
|
||||||
border: '1px solid transparent',
|
border: '1px solid var(--border-light)',
|
||||||
borderRadius: 10,
|
borderRadius: 999,
|
||||||
cursor: disabled ? 'not-allowed' : 'pointer',
|
cursor: disabled ? 'not-allowed' : 'pointer',
|
||||||
opacity: disabled ? 0.4 : 1,
|
opacity: disabled ? 0.4 : 1,
|
||||||
transition: 'background 0.12s, border-color 0.12s',
|
transition: 'background 0.12s, border-color 0.12s',
|
||||||
@@ -81,11 +110,15 @@ function PillButton({ icon, label, hint, onClick, onContextMenu, disabled,
|
|||||||
e.currentTarget.style.borderColor = 'var(--accent)'
|
e.currentTarget.style.borderColor = 'var(--accent)'
|
||||||
}}}
|
}}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
e.currentTarget.style.background = 'var(--bg-item)'
|
e.currentTarget.style.background = 'var(--bg-input)'
|
||||||
e.currentTarget.style.borderColor = 'transparent'
|
e.currentTarget.style.borderColor = 'var(--border-light)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon name={icon} size={19} style={{ color: 'var(--accent)', flexShrink: 0 }} />
|
{showIcon && (
|
||||||
|
<Icon name={icon} size={mode === 'icon' ? 18 : 16}
|
||||||
|
style={{ color: 'var(--accent)', flexShrink: 0 }} />
|
||||||
|
)}
|
||||||
|
{showLabel && (
|
||||||
<span style={{
|
<span style={{
|
||||||
display: 'flex', alignItems: 'center', gap: 2,
|
display: 'flex', alignItems: 'center', gap: 2,
|
||||||
maxWidth: '100%', overflow: 'hidden',
|
maxWidth: '100%', overflow: 'hidden',
|
||||||
@@ -97,9 +130,16 @@ function PillButton({ icon, label, hint, onClick, onContextMenu, disabled,
|
|||||||
style={{ color: 'var(--text-muted)', flexShrink: 0 }} />
|
style={{ color: 'var(--text-muted)', flexShrink: 0 }} />
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
|
)}
|
||||||
|
{/* Im Icon-Modus signalisiert ein kleines Eck-Chevron das Dropdown */}
|
||||||
|
{hasMenu && !showLabel && (
|
||||||
|
<Icon name="expand_more" size={10}
|
||||||
|
style={{ position: 'absolute', bottom: 2, right: 2,
|
||||||
|
color: 'var(--text-muted)' }} />
|
||||||
|
)}
|
||||||
{badge && (
|
{badge && (
|
||||||
<span style={{
|
<span style={{
|
||||||
position: 'absolute', top: 4, right: 4,
|
position: 'absolute', top: 3, right: 3,
|
||||||
fontSize: 8, padding: '0px 4px', borderRadius: 8,
|
fontSize: 8, padding: '0px 4px', borderRadius: 8,
|
||||||
background: 'var(--bg-section)', color: 'var(--text-muted)',
|
background: 'var(--bg-section)', color: 'var(--text-muted)',
|
||||||
}}>{badge}</span>
|
}}>{badge}</span>
|
||||||
@@ -109,9 +149,9 @@ function PillButton({ icon, label, hint, onClick, onContextMenu, disabled,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Kategorie-Gruppe: Label + einheitliches Raster (auto-fill Spalten)
|
// Kategorie-Gruppe: Label + einheitliches Raster (auto-fill Spalten)
|
||||||
function PillGroup({ label, children }) {
|
function PillGroup({ label, mode = 'both', children }) {
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
|
||||||
<span style={{
|
<span style={{
|
||||||
fontSize: 9, color: 'var(--text-muted)',
|
fontSize: 9, color: 'var(--text-muted)',
|
||||||
letterSpacing: '0.08em', textTransform: 'uppercase',
|
letterSpacing: '0.08em', textTransform: 'uppercase',
|
||||||
@@ -121,8 +161,8 @@ function PillGroup({ label, children }) {
|
|||||||
</span>
|
</span>
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gridTemplateColumns: 'repeat(auto-fill, minmax(68px, 1fr))',
|
gridTemplateColumns: `repeat(auto-fill, minmax(${TILE_MIN_COL[mode]}px, 1fr))`,
|
||||||
gap: 6,
|
gap: 5,
|
||||||
}}>
|
}}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
@@ -358,6 +398,8 @@ function NeuesElementSection({ noGeschoss, activeName, elementsCount, treppe2DSh
|
|||||||
const [treppeMenuOpen, setTreppeMenuOpen] = useState(false)
|
const [treppeMenuOpen, setTreppeMenuOpen] = useState(false)
|
||||||
const [stuetzeMenuOpen, setStuetzeMenuOpen] = useState(false)
|
const [stuetzeMenuOpen, setStuetzeMenuOpen] = useState(false)
|
||||||
const [traegerMenuOpen, setTraegerMenuOpen] = useState(false)
|
const [traegerMenuOpen, setTraegerMenuOpen] = useState(false)
|
||||||
|
const [tileMode, setTileMode] = useState(readTileMode)
|
||||||
|
const changeTileMode = (m) => { setTileMode(m); writeTileMode(m) }
|
||||||
const treppeWrapperRef = useRef(null)
|
const treppeWrapperRef = useRef(null)
|
||||||
const dis = noGeschoss
|
const dis = noGeschoss
|
||||||
const baseHint = (label) =>
|
const baseHint = (label) =>
|
||||||
@@ -435,37 +477,41 @@ function NeuesElementSection({ noGeschoss, activeName, elementsCount, treppe2DSh
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PillGroup label="Konstruktion">
|
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||||
<PillButton icon="view_week" label="Wand"
|
<TileModeDropdown mode={tileMode} onChange={changeTileMode} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<PillGroup label="Konstruktion" mode={tileMode}>
|
||||||
|
<PillButton mode={tileMode} icon="view_week" label="Wand"
|
||||||
hint={baseHint('Wand zeichnen')} disabled={dis}
|
hint={baseHint('Wand zeichnen')} disabled={dis}
|
||||||
onClick={() => createWall({ geschoss: '' })} />
|
onClick={() => createWall({ geschoss: '' })} />
|
||||||
<PillButton icon="layers" label="Decke"
|
<PillButton mode={tileMode} icon="layers" label="Decke"
|
||||||
hint={baseHint('Decke zeichnen')} disabled={dis}
|
hint={baseHint('Decke zeichnen')} disabled={dis}
|
||||||
onClick={() => createDecke({ geschoss: '' })} />
|
onClick={() => createDecke({ geschoss: '' })} />
|
||||||
<PillButton icon="roofing" label="Dach"
|
<PillButton mode={tileMode} icon="roofing" label="Dach"
|
||||||
hint={baseHint('Pultdach zeichnen — Traufe = 1. Kante')} disabled={dis}
|
hint={baseHint('Pultdach zeichnen — Traufe = 1. Kante')} disabled={dis}
|
||||||
onClick={() => createDach({ geschoss: '' })} />
|
onClick={() => createDach({ geschoss: '' })} />
|
||||||
</PillGroup>
|
</PillGroup>
|
||||||
|
|
||||||
<PillGroup label="Öffnungen">
|
<PillGroup label="Öffnungen" mode={tileMode}>
|
||||||
<PillButton icon="window" label="Fenster"
|
<PillButton mode={tileMode} icon="window" label="Fenster"
|
||||||
hint={dis ? baseHint('Fenster') :
|
hint={dis ? baseHint('Fenster') :
|
||||||
'Erst Wand-Achse wählen, dann Punkt darauf'} disabled={dis}
|
'Erst Wand-Achse wählen, dann Punkt darauf'} disabled={dis}
|
||||||
onClick={() => createFenster({})} />
|
onClick={() => createFenster({})} />
|
||||||
<PillButton icon="sensor_door" label="Tür"
|
<PillButton mode={tileMode} icon="sensor_door" label="Tür"
|
||||||
hint={dis ? baseHint('Tür') :
|
hint={dis ? baseHint('Tür') :
|
||||||
'Erst Wand-Achse wählen, dann Punkt darauf'} disabled={dis}
|
'Erst Wand-Achse wählen, dann Punkt darauf'} disabled={dis}
|
||||||
onClick={() => createTuer({})} />
|
onClick={() => createTuer({})} />
|
||||||
<PillButton icon="rectangle" label="Aussparung"
|
<PillButton mode={tileMode} icon="rectangle" label="Aussparung"
|
||||||
hint={dis ? baseHint('Aussparung') :
|
hint={dis ? baseHint('Aussparung') :
|
||||||
'Outline auf einer Decke zeichnen — wird automatisch ausgeschnitten'}
|
'Outline auf einer Decke zeichnen — wird automatisch ausgeschnitten'}
|
||||||
disabled={dis}
|
disabled={dis}
|
||||||
onClick={() => createAussparung({})} />
|
onClick={() => createAussparung({})} />
|
||||||
</PillGroup>
|
</PillGroup>
|
||||||
|
|
||||||
<PillGroup label="Erschliessung">
|
<PillGroup label="Erschliessung" mode={tileMode}>
|
||||||
<div ref={treppeWrapperRef} style={{ position: 'relative' }}>
|
<div ref={treppeWrapperRef} style={{ position: 'relative' }}>
|
||||||
<PillButton icon="stairs" label="Treppe" hasMenu
|
<PillButton mode={tileMode} icon="stairs" label="Treppe" hasMenu
|
||||||
hint={dis ? baseHint('Treppe') :
|
hint={dis ? baseHint('Treppe') :
|
||||||
'Klick: gerade Treppe · Rechtsklick: Typ wählen'}
|
'Klick: gerade Treppe · Rechtsklick: Typ wählen'}
|
||||||
disabled={dis}
|
disabled={dis}
|
||||||
@@ -478,9 +524,9 @@ function NeuesElementSection({ noGeschoss, activeName, elementsCount, treppe2DSh
|
|||||||
</div>
|
</div>
|
||||||
</PillGroup>
|
</PillGroup>
|
||||||
|
|
||||||
<PillGroup label="Tragwerk">
|
<PillGroup label="Tragwerk" mode={tileMode}>
|
||||||
<div style={{ position: 'relative' }}>
|
<div style={{ position: 'relative' }}>
|
||||||
<PillButton icon="square_foot" label="Stütze" hasMenu
|
<PillButton mode={tileMode} icon="square_foot" label="Stütze" hasMenu
|
||||||
hint={dis ? baseHint('Stütze') :
|
hint={dis ? baseHint('Stütze') :
|
||||||
'Klick: Quadrat-Stütze · Rechtsklick: Profil wählen'}
|
'Klick: Quadrat-Stütze · Rechtsklick: Profil wählen'}
|
||||||
disabled={dis}
|
disabled={dis}
|
||||||
@@ -492,7 +538,7 @@ function NeuesElementSection({ noGeschoss, activeName, elementsCount, treppe2DSh
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div style={{ position: 'relative' }}>
|
<div style={{ position: 'relative' }}>
|
||||||
<PillButton icon="horizontal_rule" label="Träger" hasMenu
|
<PillButton mode={tileMode} icon="horizontal_rule" label="Träger" hasMenu
|
||||||
hint={dis ? baseHint('Träger') :
|
hint={dis ? baseHint('Träger') :
|
||||||
'Klick: Rechteck-Träger · Rechtsklick: Profil wählen'}
|
'Klick: Rechteck-Träger · Rechtsklick: Profil wählen'}
|
||||||
disabled={dis}
|
disabled={dis}
|
||||||
@@ -505,32 +551,32 @@ function NeuesElementSection({ noGeschoss, activeName, elementsCount, treppe2DSh
|
|||||||
</div>
|
</div>
|
||||||
</PillGroup>
|
</PillGroup>
|
||||||
|
|
||||||
<PillGroup label="Raeume">
|
<PillGroup label="Raeume" mode={tileMode}>
|
||||||
<PillButton icon="crop_free" label="Raum"
|
<PillButton mode={tileMode} icon="crop_free" label="Raum"
|
||||||
hint={dis ? baseHint('Raum') :
|
hint={dis ? baseHint('Raum') :
|
||||||
'Outline zeichnen · Stempel zeigt Name + Fläche'}
|
'Outline zeichnen · Stempel zeigt Name + Fläche'}
|
||||||
disabled={dis}
|
disabled={dis}
|
||||||
onClick={() => createRaum({})} />
|
onClick={() => createRaum({})} />
|
||||||
<PillButton icon="receipt_long" label="Stempel"
|
<PillButton mode={tileMode} icon="receipt_long" label="Stempel"
|
||||||
hint={dis ? baseHint('Stempel') :
|
hint={dis ? baseHint('Stempel') :
|
||||||
'SIA-Bilanz-Stempel platzieren · Default = aktives Geschoss · Properties: Total/Geschoss umstellen'}
|
'SIA-Bilanz-Stempel platzieren · Default = aktives Geschoss · Properties: Total/Geschoss umstellen'}
|
||||||
disabled={dis}
|
disabled={dis}
|
||||||
onClick={() => createStempel({})} />
|
onClick={() => createStempel({})} />
|
||||||
</PillGroup>
|
</PillGroup>
|
||||||
|
|
||||||
<PillGroup label="Library">
|
<PillGroup label="Library" mode={tileMode}>
|
||||||
<PillButton icon="inventory_2" label="Symbol"
|
<PillButton mode={tileMode} icon="inventory_2" label="Symbol"
|
||||||
hint={dis ? baseHint('Symbol') :
|
hint={dis ? baseHint('Symbol') :
|
||||||
'Library-Picker oeffnen · Item waehlen · im Viewport Punkt klicken zum Platzieren'}
|
'Library-Picker oeffnen · Item waehlen · im Viewport Punkt klicken zum Platzieren'}
|
||||||
disabled={dis}
|
disabled={dis}
|
||||||
onClick={() => listLibrary()} />
|
onClick={() => listLibrary()} />
|
||||||
</PillGroup>
|
</PillGroup>
|
||||||
|
|
||||||
<PillGroup label="Importer">
|
<PillGroup label="Importer" mode={tileMode}>
|
||||||
<PillButton icon="map" label="Swisstopo"
|
<PillButton mode={tileMode} icon="map" label="Swisstopo"
|
||||||
hint="Vollautomatischer Import via swisstopo STAC-API: Adresse suchen, Radius wählen, Gebäude + Terrain + Luftbild holen"
|
hint="Vollautomatischer Import via swisstopo STAC-API: Adresse suchen, Radius wählen, Gebäude + Terrain + Luftbild holen"
|
||||||
onClick={() => openSwisstopoDialog()} />
|
onClick={() => openSwisstopoDialog()} />
|
||||||
<PillButton icon="public" label="OSM"
|
<PillButton mode={tileMode} icon="public" label="OSM"
|
||||||
hint="OpenStreetMap-Daten via Overpass-API als 2D-Linien: Strassen, Gebäudeumrisse, Wasser, Grünflächen, Wege"
|
hint="OpenStreetMap-Daten via Overpass-API als 2D-Linien: Strassen, Gebäudeumrisse, Wasser, Grünflächen, Wege"
|
||||||
onClick={() => openOsmDialog()} />
|
onClick={() => openOsmDialog()} />
|
||||||
</PillGroup>
|
</PillGroup>
|
||||||
|
|||||||
+55
-17
@@ -1,9 +1,20 @@
|
|||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
// Copyright (C) 2026 Karim Gabriele Varano
|
// Copyright (C) 2026 Karim Gabriele Varano
|
||||||
import { useEffect } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import Icon from './components/Icon'
|
import Icon from './components/Icon'
|
||||||
import { notifyReady, runRhinoCommand } from './lib/rhinoBridge'
|
import { notifyReady, runRhinoCommand } from './lib/rhinoBridge'
|
||||||
|
|
||||||
|
// Anzeige-Modus der Kacheln, geteilt mit Elemente-Panel via localStorage.
|
||||||
|
const TILE_MODE_KEY = 'dossier_tile_mode' // 'both' | 'icon' | 'text'
|
||||||
|
function readTileMode() {
|
||||||
|
try { return localStorage.getItem(TILE_MODE_KEY) || 'both' } catch { return 'both' }
|
||||||
|
}
|
||||||
|
function writeTileMode(m) {
|
||||||
|
try { localStorage.setItem(TILE_MODE_KEY, m) } catch { /* WebView ohne Storage */ }
|
||||||
|
}
|
||||||
|
const TILE_MIN_COL = { icon: 40, text: 66, both: 58 }
|
||||||
|
const TILE_MIN_H = { icon: 38, text: 30, both: 46 }
|
||||||
|
|
||||||
// Tool-Definitionen: [icon, label, rhino-command, tooltip]
|
// Tool-Definitionen: [icon, label, rhino-command, tooltip]
|
||||||
// Material-Symbol-Namen siehe https://fonts.google.com/icons
|
// Material-Symbol-Namen siehe https://fonts.google.com/icons
|
||||||
const TOOLS = {
|
const TOOLS = {
|
||||||
@@ -54,7 +65,9 @@ const TOOLS = {
|
|||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
function ToolTile({ icon, label, cmd, tip }) {
|
function ToolTile({ icon, label, cmd, tip, mode }) {
|
||||||
|
const showIcon = mode !== 'text'
|
||||||
|
const showLabel = mode !== 'icon'
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={() => runRhinoCommand(cmd)}
|
onClick={() => runRhinoCommand(cmd)}
|
||||||
@@ -64,17 +77,17 @@ function ToolTile({ icon, label, cmd, tip }) {
|
|||||||
e.currentTarget.style.background = 'var(--bg-item-hover)'
|
e.currentTarget.style.background = 'var(--bg-item-hover)'
|
||||||
}}
|
}}
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
e.currentTarget.style.borderColor = 'transparent'
|
e.currentTarget.style.borderColor = 'var(--border-light)'
|
||||||
e.currentTarget.style.background = 'var(--bg-item)'
|
e.currentTarget.style.background = 'var(--bg-input)'
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
display: 'flex', flexDirection: 'column',
|
display: 'flex', flexDirection: 'column',
|
||||||
alignItems: 'center', justifyContent: 'center', gap: 6,
|
alignItems: 'center', justifyContent: 'center', gap: 4,
|
||||||
padding: '10px 4px',
|
padding: '6px 4px',
|
||||||
minHeight: 56,
|
minHeight: TILE_MIN_H[mode],
|
||||||
background: 'var(--bg-item)',
|
background: 'var(--bg-input)',
|
||||||
border: '1px solid transparent',
|
border: '1px solid var(--border-light)',
|
||||||
borderRadius: 10,
|
borderRadius: 999,
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
transition: 'background 0.12s, border-color 0.12s',
|
transition: 'background 0.12s, border-color 0.12s',
|
||||||
fontSize: 10, fontWeight: 500,
|
fontSize: 10, fontWeight: 500,
|
||||||
@@ -82,18 +95,23 @@ function ToolTile({ icon, label, cmd, tip }) {
|
|||||||
appearance: 'none', WebkitAppearance: 'none',
|
appearance: 'none', WebkitAppearance: 'none',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Icon name={icon} size={19} style={{ color: 'var(--accent)', flexShrink: 0 }} />
|
{showIcon && (
|
||||||
|
<Icon name={icon} size={mode === 'icon' ? 18 : 16}
|
||||||
|
style={{ color: 'var(--accent)', flexShrink: 0 }} />
|
||||||
|
)}
|
||||||
|
{showLabel && (
|
||||||
<span style={{
|
<span style={{
|
||||||
maxWidth: '100%', overflow: 'hidden',
|
maxWidth: '100%', overflow: 'hidden',
|
||||||
textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
||||||
}}>{label}</span>
|
}}>{label}</span>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function GridSection({ label, children }) {
|
function GridSection({ label, mode, children }) {
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
|
||||||
<span style={{
|
<span style={{
|
||||||
fontSize: 9, color: 'var(--text-muted)',
|
fontSize: 9, color: 'var(--text-muted)',
|
||||||
letterSpacing: '0.08em', textTransform: 'uppercase',
|
letterSpacing: '0.08em', textTransform: 'uppercase',
|
||||||
@@ -103,8 +121,8 @@ function GridSection({ label, children }) {
|
|||||||
</span>
|
</span>
|
||||||
<div style={{
|
<div style={{
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
gridTemplateColumns: 'repeat(auto-fill, minmax(68px, 1fr))',
|
gridTemplateColumns: `repeat(auto-fill, minmax(${TILE_MIN_COL[mode]}px, 1fr))`,
|
||||||
gap: 6,
|
gap: 5,
|
||||||
}}>
|
}}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
@@ -112,11 +130,28 @@ function GridSection({ label, children }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function TileModeDropdown({ mode, onChange }) {
|
||||||
|
return (
|
||||||
|
<select
|
||||||
|
value={mode}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
title="Anzeige: Symbol / Text / Symbol + Text"
|
||||||
|
style={{ fontSize: 10, padding: '3px 6px', maxWidth: 130 }}
|
||||||
|
>
|
||||||
|
<option value="icon">Symbol</option>
|
||||||
|
<option value="text">Text</option>
|
||||||
|
<option value="both">Symbol + Text</option>
|
||||||
|
</select>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export default function WerkzeugeApp() {
|
export default function WerkzeugeApp() {
|
||||||
|
const [mode, setMode] = useState(readTileMode)
|
||||||
useEffect(() => { notifyReady() }, [])
|
useEffect(() => { notifyReady() }, [])
|
||||||
|
|
||||||
|
const changeMode = (m) => { setMode(m); writeTileMode(m) }
|
||||||
const groups = Object.entries(TOOLS)
|
const groups = Object.entries(TOOLS)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -129,10 +164,13 @@ export default function WerkzeugeApp() {
|
|||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
overflowY: 'auto', overflowX: 'hidden',
|
overflowY: 'auto', overflowX: 'hidden',
|
||||||
}}>
|
}}>
|
||||||
|
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
||||||
|
<TileModeDropdown mode={mode} onChange={changeMode} />
|
||||||
|
</div>
|
||||||
{groups.map(([title, items]) => (
|
{groups.map(([title, items]) => (
|
||||||
<GridSection key={title} label={title}>
|
<GridSection key={title} label={title} mode={mode}>
|
||||||
{items.map(([icon, label, cmd, tip]) => (
|
{items.map(([icon, label, cmd, tip]) => (
|
||||||
<ToolTile key={cmd} icon={icon} label={label} cmd={cmd} tip={tip} />
|
<ToolTile key={cmd} icon={icon} label={label} cmd={cmd} tip={tip} mode={mode} />
|
||||||
))}
|
))}
|
||||||
</GridSection>
|
</GridSection>
|
||||||
))}
|
))}
|
||||||
|
|||||||
Reference in New Issue
Block a user