diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 38d3a83..e16dafa 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -389,7 +389,7 @@ function BottomNav({ active, setActive, user, onLogout, unreadMsgs=0 }) { // ── Dashboard Widgets ───────────────────────────────────────────────────────── // ── QuickLinks Carousel (Mobile) ───────────────────────────────────────────── -function QuickLinksCarousel({ links, onFolderClick }) { +function QuickLinksCarousel({ links, onFolderClick, navigateTool }) { const perRow = 4; const gap = 6; const pageSize = perRow * 2; @@ -413,7 +413,7 @@ function QuickLinksCarousel({ links, onFolderClick }) { if (pageCount <= 1) return (
- {links.map(l => )} + {links.map(l => )}
); @@ -431,7 +431,7 @@ function QuickLinksCarousel({ links, onFolderClick }) { gap, flexShrink:0, scrollSnapAlign:'start', width:'100%', minWidth:'100%', boxSizing:'border-box', }}> - {links.slice(pi*pageSize, (pi+1)*pageSize).map(l => )} + {links.slice(pi*pageSize, (pi+1)*pageSize).map(l => )} ))} @@ -452,19 +452,20 @@ function QuickLinksCarousel({ links, onFolderClick }) { ); } -function QuickLinkIcon({ l, onFolderClick }) { +function QuickLinkIcon({ l, onFolderClick, navigateTool }) { const [imgOk, setImgOk] = useState(true); + const isTool = l.url?.startsWith('tool://'); const hasCustomIcon = l.icon && l.icon !== '🔗'; - const faviconUrl = (!hasCustomIcon && !l.isFolder) ? (() => { + const faviconUrl = (!hasCustomIcon && !l.isFolder && !isTool) ? (() => { try { return `https://www.google.com/s2/favicons?sz=64&domain=${new URL(l.url).hostname}`; } catch { return null; } })() : null; - const showFavicon = !hasCustomIcon && !!faviconUrl && imgOk && !l.isFolder; + const showFavicon = !hasCustomIcon && !!faviconUrl && imgOk && !l.isFolder && !isTool; const showCustomImg = hasCustomIcon && !l.isFolder && l.icon.startsWith('http'); const showEmoji = !showFavicon && !showCustomImg; - const emoji = l.isFolder ? (l.icon||'📁') : (hasCustomIcon ? l.icon : '🔗'); + const emoji = l.isFolder ? (l.icon||'📁') : (hasCustomIcon ? l.icon : (isTool ? '⚡' : '🔗')); const content = ( <> @@ -477,10 +478,13 @@ function QuickLinkIcon({ l, onFolderClick }) { ); const baseStyle = {display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',gap:3, - padding:'4px 2px',background:'rgba(255,255,255,0.04)', - border: l.isFolder ? '1px solid rgba(78,205,196,0.25)' : '1px solid rgba(255,255,255,0.08)', + padding:'4px 2px',background: isTool ? 'rgba(160,132,250,0.06)' : 'rgba(255,255,255,0.04)', + border: l.isFolder ? '1px solid rgba(78,205,196,0.25)' : isTool ? '1px solid rgba(160,132,250,0.2)' : '1px solid rgba(255,255,255,0.08)', borderRadius:10,textDecoration:'none',height:48,width:'100%',boxSizing:'border-box',cursor:'pointer'}; + if (isTool) { + return ; + } if (l.isFolder && onFolderClick) { const realId = typeof l.id==='string'&&l.id.startsWith('folder-') ? parseInt(l.id.replace('folder-','')) : l.id; return ; @@ -488,7 +492,7 @@ function QuickLinkIcon({ l, onFolderClick }) { return {content}; } -function FolderModal({ folder, onClose }) { +function FolderModal({ folder, onClose, navigateTool }) { const [links, setLinks] = useState([]); const [loading, setLoading] = useState(true); useEffect(()=>{ @@ -514,7 +518,7 @@ function FolderModal({ folder, onClose }) { {loading ?
: links.length===0 ?
Ordner ist leer
:
- {links.map(l=>)} + {links.map(l=>)}
} @@ -522,13 +526,27 @@ function FolderModal({ folder, onClose }) { ); } -function QuickLinks({ toast, mobile }) { +function QuickLinks({ toast, mobile, setActive, setDevToolsNav }) { const [links, setLinks] = useState([]); const [folderModal, setFolderModal] = useState(null); const [editMode, setEditMode] = useState(false); useEffect(() => { api('/dashboard/links').then(setLinks).catch(() => {}); }, []); + // Tool-Navigation für tool://-Links + const navigateTool = (url) => { + if (!url?.startsWith('tool://')) return; + const withoutScheme = url.replace('tool://', ''); + const [toolId, query] = withoutScheme.split('?'); + const sub = query?.replace('sub=', ''); + if (toolId === 'devtools' && sub && setDevToolsNav) { + setActive('devtools'); + setDevToolsNav({ tool: sub, ts: Date.now() }); + } else if (setActive) { + setActive(toolId); + } + }; + const legacyItems = links.filter(l => l.item_type === 'quicklink' || !l.item_type); const hasLegacy = legacyItems.length > 0; @@ -547,7 +565,7 @@ function QuickLinks({ toast, mobile }) { return (
- {folderModal && setFolderModal(null)}/>} + {folderModal && setFolderModal(null)} navigateTool={navigateTool}/>}
SCHNELLZUGRIFF
{hasLegacy && ( @@ -591,9 +609,9 @@ function QuickLinks({ toast, mobile }) { Links oder Ordner in der Linkliste mit ● Schnellzugriff aktivieren.
: mobile - ? + ? :
- {allItems.map(l => )} + {allItems.map(l => )}
}
@@ -1947,7 +1965,7 @@ function Dashboard({ toast, mobile, setActive, setDevToolsNav, unreadMsgs=0, unr {/* Globale Suche */} - + diff --git a/frontend/src/tools/linkliste.jsx b/frontend/src/tools/linkliste.jsx index da67b2b..e2624ab 100644 --- a/frontend/src/tools/linkliste.jsx +++ b/frontend/src/tools/linkliste.jsx @@ -4,6 +4,122 @@ import { api, S } from '../lib.js'; const LINK_ICONS = ['🔗','📄','🌐','📊','🎯','⚙️','📁','🔧','📱','💡','🏠','🛒','📺','🎮','🎵','📧','💬','📰','🔬','🏥','🏦','✈️','🚀','🔐','📂','⭐','🔑','💻','🖥','📡']; const FOLDER_ICONS = ['📁','📂','🗂','💼','🗃','📦','🏷','🔖','📋','📌','🗓','🖇']; +// ── Alle DockStation-Tools & Unterfunktionen ────────────────────────────────── +const TOOL_ENTRIES = [ + { url:'tool://statistik', label:'📊 Statistik', group:'3D-Druck' }, + { url:'tool://bestellungen', label:'📦 Bestellungen', group:'3D-Druck' }, + { url:'tool://kalkulator3d', label:'🧮 Kostenrechner', group:'3D-Druck' }, + { url:'tool://kanban', label:'🗂 Kanban', group:'Werkzeuge' }, + { url:'tool://dateien', label:'📁 Dateien', group:'Werkzeuge' }, + { url:'tool://nachrichten', label:'💬 Nachrichten', group:'Werkzeuge' }, + { url:'tool://linkliste', label:'🔗 Linkliste', group:'Werkzeuge' }, + { url:'tool://codeschnipsel', label:' Code-Schnipsel', group:'Werkzeuge' }, + { url:'tool://media', label:'🎬 Media / Kino', group:'Freizeit' }, + { url:'tool://devtools', label:'🔧 Dev-Tools (alle)', group:'Dev-Tools' }, + { url:'tool://devtools?sub=cron', label:'⏱ Crontab', group:'Dev-Tools' }, + { url:'tool://devtools?sub=elektro', label:'⚡ Elektro', group:'Dev-Tools' }, + { url:'tool://devtools?sub=json', label:'{ } JSON', group:'Dev-Tools' }, + { url:'tool://devtools?sub=regex', label:'🔍 Regex', group:'Dev-Tools' }, + { url:'tool://devtools?sub=diff', label:'± Text Diff', group:'Dev-Tools' }, + { url:'tool://devtools?sub=netzwerk', label:'🌐 Netzwerk', group:'Dev-Tools' }, + { url:'tool://devtools?sub=datetime', label:'📅 Datetime', group:'Dev-Tools' }, + { url:'tool://devtools?sub=farben', label:'🎨 Farben', group:'Dev-Tools' }, + { url:'tool://devtools?sub=keygen', label:'🔐 Key-Gen', group:'Dev-Tools' }, + { url:'tool://devtools?sub=qrcode', label:'◻ QR-Code', group:'Dev-Tools' }, +]; + +// ── Tool-Picker Modal ───────────────────────────────────────────────────────── +function ToolPicker({ folders, onSave, onClose, toast }) { + const [search, setSearch] = useState(''); + const [folderId, setFolderId] = useState(null); + const [saving, setSaving] = useState(null); + const mob = isMob(); + + const filtered = TOOL_ENTRIES.filter(t => + !search || t.label.toLowerCase().includes(search.toLowerCase()) || t.group.toLowerCase().includes(search.toLowerCase()) + ); + + const groups = [...new Set(filtered.map(t => t.group))]; + + const pick = async (entry) => { + setSaving(entry.url); + try { + await onSave({ title: entry.label.replace(/^[^\s]+\s/, ''), url: entry.url, icon: entry.label.split(' ')[0], folder_id: folderId, in_quickaccess: 1 }); + toast(`${entry.label} zum Schnellzugriff hinzugefügt ✓`); + onClose(); + } catch(e) { toast(e.message, 'error'); setSaving(null); } + }; + + return ( +
e.target===e.currentTarget&&onClose()} + style={{position:'fixed',inset:0,background:'rgba(0,0,0,0.75)',zIndex:6000, + display:'flex',alignItems:mob?'flex-end':'center',justifyContent:'center',padding:mob?0:20}}> +
+ + {/* Header */} +
+ {mob &&
} +
+
APP HINZUFÜGEN
+ +
+ {/* Ordner-Auswahl */} + {folders.length > 0 && ( +
+
IN ORDNER (optional)
+
+ + {folders.map(f=>( + + ))} +
+
+ )} + setSearch(e.target.value)} + placeholder="App oder Funktion suchen…" + style={{...S.inp,marginBottom:10,fontSize:12}}/> +
+ + {/* Liste */} +
+ {groups.map(group=>( +
+
+ {group.toUpperCase()} +
+ {filtered.filter(t=>t.group===group).map(entry=>( + + ))} +
+ ))} +
+
+
+ ); +} + function extIcon(url) { try { return `https://www.google.com/s2/favicons?sz=32&domain=${new URL(url).hostname}`; } catch { return null; } @@ -203,6 +319,7 @@ export default function Linkliste({ toast, mobile }) { const [tab, setTab] = useState('own'); const [showForm, setShowForm] = useState(false); const [showFolderForm, setShowFolderForm] = useState(false); + const [showToolPicker, setShowToolPicker] = useState(false); const [editItem, setEditItem] = useState(null); const [editFolder,setEditFolder]= useState(null); const [shareItem, setShareItem] = useState(null); @@ -507,6 +624,10 @@ export default function Linkliste({ toast, mobile }) { )} + {!showForm && !editItem && !sortMode && ( + + )} {!showForm && !editItem && !sortMode && ( @@ -517,6 +638,7 @@ export default function Linkliste({ toast, mobile }) {
{/* Formulare */} + {showToolPicker && setShowToolPicker(false)} toast={toast}/>} {showFolderForm && !editFolder && setShowFolderForm(false)} toast={toast}/>} {editFolder && setEditFolder(null)} toast={toast}/>} {showForm && !editItem && setShowForm(false)} toast={toast}/>}