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 &&
}
+
+ {/* 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}/>}