Linkliste: Apps & Dev-Tools-Unterfunktionen als Schnellzugriff hinzufügbar (tool://-Schema)

This commit is contained in:
2026-06-24 23:15:35 +02:00
parent 8f8e49f749
commit e6c80ab7ee
2 changed files with 156 additions and 16 deletions

View File

@@ -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 (
<div onClick={e=>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}}>
<div style={{background:'#1a1a1e',border:'1px solid rgba(255,255,255,0.12)',
borderRadius:mob?'16px 16px 0 0':14,width:'100%',maxWidth:480,
display:'flex',flexDirection:'column',maxHeight:mob?'85vh':'80vh',
paddingBottom:mob?'calc(56px + env(safe-area-inset-bottom,0px))':0}}>
{/* Header */}
<div style={{padding:'18px 20px 0',flexShrink:0}}>
{mob && <div style={{width:36,height:4,background:'rgba(255,255,255,0.15)',borderRadius:2,margin:'0 auto 14px'}}/>}
<div style={{display:'flex',justifyContent:'space-between',alignItems:'center',marginBottom:12}}>
<div style={{...S.head,marginBottom:0}}>APP HINZUFÜGEN</div>
<button onClick={onClose} style={{background:'transparent',border:'none',color:'rgba(255,255,255,0.4)',cursor:'pointer',fontSize:18}}></button>
</div>
{/* Ordner-Auswahl */}
{folders.length > 0 && (
<div style={{marginBottom:10}}>
<div style={{fontSize:9,fontFamily:'monospace',color:'rgba(255,255,255,0.35)',letterSpacing:1,marginBottom:5}}>IN ORDNER (optional)</div>
<div style={{display:'flex',gap:5,flexWrap:'wrap'}}>
<button onClick={()=>setFolderId(null)} style={{
...S.btn(folderId===null?'#4ecdc4':'#888888',true),fontSize:10,padding:'3px 8px'}}>
Kein Ordner
</button>
{folders.map(f=>(
<button key={f.id} onClick={()=>setFolderId(f.id)} style={{
...S.btn(folderId===f.id?'#4ecdc4':'#888888',true),fontSize:10,padding:'3px 8px'}}>
{f.icon||'📁'} {f.name}
</button>
))}
</div>
</div>
)}
<input value={search} onChange={e=>setSearch(e.target.value)}
placeholder="App oder Funktion suchen…"
style={{...S.inp,marginBottom:10,fontSize:12}}/>
</div>
{/* Liste */}
<div style={{overflowY:'auto',padding:'0 20px 20px',scrollbarWidth:'thin',scrollbarColor:'rgba(255,255,255,0.08) transparent'}}>
{groups.map(group=>(
<div key={group} style={{marginBottom:10}}>
<div style={{fontSize:9,fontFamily:'monospace',color:'rgba(255,255,255,0.3)',letterSpacing:1,marginBottom:5,paddingTop:4}}>
{group.toUpperCase()}
</div>
{filtered.filter(t=>t.group===group).map(entry=>(
<button key={entry.url} onClick={()=>pick(entry)} disabled={!!saving}
style={{display:'flex',alignItems:'center',gap:10,width:'100%',
background:saving===entry.url?'rgba(78,205,196,0.1)':'rgba(255,255,255,0.03)',
border:'1px solid rgba(255,255,255,0.07)',borderRadius:8,
padding:'9px 12px',marginBottom:4,cursor:'pointer',textAlign:'left',
transition:'background 0.12s'}}>
<span style={{fontSize:16,flexShrink:0}}>{entry.label.split(' ')[0]}</span>
<span style={{flex:1,fontSize:12,fontFamily:'monospace',color:'#fff'}}>
{entry.label.replace(/^[^\s]+\s/, '')}
</span>
{saving===entry.url
? <span style={{fontSize:10,color:'#4ecdc4',fontFamily:'monospace'}}></span>
: <span style={{fontSize:10,color:'rgba(255,255,255,0.25)',fontFamily:'monospace'}}>+ hinzufügen</span>}
</button>
))}
</div>
))}
</div>
</div>
</div>
);
}
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 }) {
<button onClick={()=>{setShowFolderForm(true);setShowForm(false);}}
style={{...S.btn('#ffe66d',true),fontSize:11,padding:'6px 12px'}}>+ Ordner</button>
)}
{!showForm && !editItem && !sortMode && (
<button onClick={()=>{setShowToolPicker(true);setShowForm(false);setShowFolderForm(false);}}
style={{...S.btn('#a78bfa',true),fontSize:11,padding:'6px 12px'}}>+ App</button>
)}
{!showForm && !editItem && !sortMode && (
<button onClick={()=>{setShowForm(true);setShowFolderForm(false);setEditItem(null);}}
style={{...S.btn('#4ecdc4'),padding:'7px 16px',fontSize:12}}>+ Link</button>
@@ -517,6 +638,7 @@ export default function Linkliste({ toast, mobile }) {
</div>
{/* Formulare */}
{showToolPicker && <ToolPicker folders={folders} onSave={addLink} onClose={()=>setShowToolPicker(false)} toast={toast}/>}
{showFolderForm && !editFolder && <FolderForm onSave={addFolder} onCancel={()=>setShowFolderForm(false)} toast={toast}/>}
{editFolder && <FolderForm initial={editFolder} onSave={updateFolder} onCancel={()=>setEditFolder(null)} toast={toast}/>}
{showForm && !editItem && <LinkForm folders={folders} onSave={addLink} onCancel={()=>setShowForm(false)} toast={toast}/>}