@rslsp1/fa-app-tools 0.1.11 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -4,6 +4,57 @@ import { Node, Edge } from '@xyflow/react';
4
4
 
5
5
  declare function useOnClickOutside(ref: RefObject<HTMLElement | null>, handler: (e: MouseEvent | TouchEvent) => void): void;
6
6
 
7
+ interface WorkspaceTags {
8
+ by_category: Record<string, string[]>;
9
+ all: Array<{
10
+ label: string;
11
+ value: string;
12
+ category: string;
13
+ }>;
14
+ }
15
+ interface Generation {
16
+ id: string;
17
+ nodeId: string;
18
+ base64?: string;
19
+ mediaId?: string;
20
+ prompt?: string;
21
+ hierarchy?: string;
22
+ seed?: number;
23
+ model?: string;
24
+ timestamp: number;
25
+ status: 'processing' | 'done' | 'error';
26
+ step?: string;
27
+ tags: string[];
28
+ type?: 'generation' | 'import';
29
+ error?: {
30
+ message: string;
31
+ details?: string;
32
+ };
33
+ selectedForExport?: boolean;
34
+ }
35
+ interface ProjectSettings {
36
+ aspectRatio: string;
37
+ selectedModel: string;
38
+ seed: number;
39
+ seedMode: 'random' | 'fixed';
40
+ }
41
+
42
+ /**
43
+ * Hook für die Tastaturnavigation im Detailview.
44
+ * Rechts = Index + 1 (älter), Links = Index - 1 (neuer).
45
+ */
46
+ declare function useKeyboardNavigation(history: Generation[], currentResult: Generation | null, setCurrentResult: (gen: Generation) => void): void;
47
+
48
+ /**
49
+ * Erstellt einen lesbaren Zeitstempel im Format YYYYMMDD_HHMMSS.
50
+ * Dies ist das vom User explizit gewünschte Format für Exporte.
51
+ */
52
+ declare const getFormattedTimestamp: () => string;
53
+ /**
54
+ * Hilfskonstante für CSS-Styles (Dark Scrollbar etc.)
55
+ */
56
+ declare const GLOBAL_STYLES = "\n .dark-scrollbar::-webkit-scrollbar { width: 4px; height: 4px; }\n .dark-scrollbar::-webkit-scrollbar-track { background: transparent; }\n .dark-scrollbar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 10px; }\n html, body, #root { margin: 0; padding: 0; width: 100%; height: 100%; background: #0e0e0e; font-family: 'Google Sans Text', sans-serif; overflow: hidden; }\n @keyframes prompt-shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }\n .prompt-loading { background: linear-gradient(90deg, rgba(255,255,255,0.02) 25%, rgba(255,255,255,0.05) 50%, rgba(255,255,255,0.02) 75%); background-size: 200% 100%; animation: prompt-shimmer 2s infinite linear; }\n @keyframes dropdown-enter { from { opacity: 0; transform: scale(0.95) translateY(-5px); } to { opacity: 1; transform: scale(1) translateY(0); } }\n .animate-dropdown { animation: dropdown-enter 0.1s ease-out forwards; }\n";
57
+
7
58
  declare function injectXMPMetadata(base64: string, prompt: string, seed?: number, model?: string, id?: string, tags?: string[], hierarchy?: string): string;
8
59
 
9
60
  /**
@@ -59,41 +110,6 @@ interface CompactDropdownProps {
59
110
  }
60
111
  declare const CompactDropdown: React.FC<CompactDropdownProps>;
61
112
 
62
- interface WorkspaceTags {
63
- by_category: Record<string, string[]>;
64
- all: Array<{
65
- label: string;
66
- value: string;
67
- category: string;
68
- }>;
69
- }
70
- interface Generation {
71
- id: string;
72
- nodeId: string;
73
- base64?: string;
74
- mediaId?: string;
75
- prompt?: string;
76
- hierarchy?: string;
77
- seed?: number;
78
- model?: string;
79
- timestamp: number;
80
- status: 'processing' | 'done' | 'error';
81
- step?: string;
82
- tags: string[];
83
- type?: 'generation' | 'import';
84
- error?: {
85
- message: string;
86
- details?: string;
87
- };
88
- selectedForExport?: boolean;
89
- }
90
- interface ProjectSettings {
91
- aspectRatio: string;
92
- selectedModel: string;
93
- seed: number;
94
- seedMode: 'random' | 'fixed';
95
- }
96
-
97
113
  interface HistoryPanelProps {
98
114
  history: Generation[];
99
115
  currentResultId: string | null;
@@ -150,4 +166,4 @@ interface ListViewProps {
150
166
  }
151
167
  declare function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMoveNode, onIndentNode, onOutdentNode, onAddSibling, focusedNodeId, onFocus, activePath, onGenerate, onGenerateBranch, onGenerateSubtree, isGeneratingNodeId }: ListViewProps): react_jsx_runtime.JSX.Element;
152
168
 
153
- export { CompactDropdown, type ExtractedCharacter, FaToolsBadge, type Generation, HistoryPanel, InspectPanel, ListView, MediaLibrary, PillButton, type ProjectSettings, SectionLabel, SetupPanel, type WorkspaceTags, exportProjectToZip, formatTreeToMarkdown, importProjectFromZip, injectXMPMetadata, parsePromptFile, useOnClickOutside };
169
+ export { CompactDropdown, type ExtractedCharacter, FaToolsBadge, GLOBAL_STYLES, type Generation, HistoryPanel, InspectPanel, ListView, MediaLibrary, PillButton, type ProjectSettings, SectionLabel, SetupPanel, type WorkspaceTags, exportProjectToZip, formatTreeToMarkdown, getFormattedTimestamp, importProjectFromZip, injectXMPMetadata, parsePromptFile, useKeyboardNavigation, useOnClickOutside };
package/dist/index.d.ts CHANGED
@@ -4,6 +4,57 @@ import { Node, Edge } from '@xyflow/react';
4
4
 
5
5
  declare function useOnClickOutside(ref: RefObject<HTMLElement | null>, handler: (e: MouseEvent | TouchEvent) => void): void;
6
6
 
7
+ interface WorkspaceTags {
8
+ by_category: Record<string, string[]>;
9
+ all: Array<{
10
+ label: string;
11
+ value: string;
12
+ category: string;
13
+ }>;
14
+ }
15
+ interface Generation {
16
+ id: string;
17
+ nodeId: string;
18
+ base64?: string;
19
+ mediaId?: string;
20
+ prompt?: string;
21
+ hierarchy?: string;
22
+ seed?: number;
23
+ model?: string;
24
+ timestamp: number;
25
+ status: 'processing' | 'done' | 'error';
26
+ step?: string;
27
+ tags: string[];
28
+ type?: 'generation' | 'import';
29
+ error?: {
30
+ message: string;
31
+ details?: string;
32
+ };
33
+ selectedForExport?: boolean;
34
+ }
35
+ interface ProjectSettings {
36
+ aspectRatio: string;
37
+ selectedModel: string;
38
+ seed: number;
39
+ seedMode: 'random' | 'fixed';
40
+ }
41
+
42
+ /**
43
+ * Hook für die Tastaturnavigation im Detailview.
44
+ * Rechts = Index + 1 (älter), Links = Index - 1 (neuer).
45
+ */
46
+ declare function useKeyboardNavigation(history: Generation[], currentResult: Generation | null, setCurrentResult: (gen: Generation) => void): void;
47
+
48
+ /**
49
+ * Erstellt einen lesbaren Zeitstempel im Format YYYYMMDD_HHMMSS.
50
+ * Dies ist das vom User explizit gewünschte Format für Exporte.
51
+ */
52
+ declare const getFormattedTimestamp: () => string;
53
+ /**
54
+ * Hilfskonstante für CSS-Styles (Dark Scrollbar etc.)
55
+ */
56
+ declare const GLOBAL_STYLES = "\n .dark-scrollbar::-webkit-scrollbar { width: 4px; height: 4px; }\n .dark-scrollbar::-webkit-scrollbar-track { background: transparent; }\n .dark-scrollbar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 10px; }\n html, body, #root { margin: 0; padding: 0; width: 100%; height: 100%; background: #0e0e0e; font-family: 'Google Sans Text', sans-serif; overflow: hidden; }\n @keyframes prompt-shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }\n .prompt-loading { background: linear-gradient(90deg, rgba(255,255,255,0.02) 25%, rgba(255,255,255,0.05) 50%, rgba(255,255,255,0.02) 75%); background-size: 200% 100%; animation: prompt-shimmer 2s infinite linear; }\n @keyframes dropdown-enter { from { opacity: 0; transform: scale(0.95) translateY(-5px); } to { opacity: 1; transform: scale(1) translateY(0); } }\n .animate-dropdown { animation: dropdown-enter 0.1s ease-out forwards; }\n";
57
+
7
58
  declare function injectXMPMetadata(base64: string, prompt: string, seed?: number, model?: string, id?: string, tags?: string[], hierarchy?: string): string;
8
59
 
9
60
  /**
@@ -59,41 +110,6 @@ interface CompactDropdownProps {
59
110
  }
60
111
  declare const CompactDropdown: React.FC<CompactDropdownProps>;
61
112
 
62
- interface WorkspaceTags {
63
- by_category: Record<string, string[]>;
64
- all: Array<{
65
- label: string;
66
- value: string;
67
- category: string;
68
- }>;
69
- }
70
- interface Generation {
71
- id: string;
72
- nodeId: string;
73
- base64?: string;
74
- mediaId?: string;
75
- prompt?: string;
76
- hierarchy?: string;
77
- seed?: number;
78
- model?: string;
79
- timestamp: number;
80
- status: 'processing' | 'done' | 'error';
81
- step?: string;
82
- tags: string[];
83
- type?: 'generation' | 'import';
84
- error?: {
85
- message: string;
86
- details?: string;
87
- };
88
- selectedForExport?: boolean;
89
- }
90
- interface ProjectSettings {
91
- aspectRatio: string;
92
- selectedModel: string;
93
- seed: number;
94
- seedMode: 'random' | 'fixed';
95
- }
96
-
97
113
  interface HistoryPanelProps {
98
114
  history: Generation[];
99
115
  currentResultId: string | null;
@@ -150,4 +166,4 @@ interface ListViewProps {
150
166
  }
151
167
  declare function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMoveNode, onIndentNode, onOutdentNode, onAddSibling, focusedNodeId, onFocus, activePath, onGenerate, onGenerateBranch, onGenerateSubtree, isGeneratingNodeId }: ListViewProps): react_jsx_runtime.JSX.Element;
152
168
 
153
- export { CompactDropdown, type ExtractedCharacter, FaToolsBadge, type Generation, HistoryPanel, InspectPanel, ListView, MediaLibrary, PillButton, type ProjectSettings, SectionLabel, SetupPanel, type WorkspaceTags, exportProjectToZip, formatTreeToMarkdown, importProjectFromZip, injectXMPMetadata, parsePromptFile, useOnClickOutside };
169
+ export { CompactDropdown, type ExtractedCharacter, FaToolsBadge, GLOBAL_STYLES, type Generation, HistoryPanel, InspectPanel, ListView, MediaLibrary, PillButton, type ProjectSettings, SectionLabel, SetupPanel, type WorkspaceTags, exportProjectToZip, formatTreeToMarkdown, getFormattedTimestamp, importProjectFromZip, injectXMPMetadata, parsePromptFile, useKeyboardNavigation, useOnClickOutside };
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  CompactDropdown: () => CompactDropdown,
34
34
  FaToolsBadge: () => FaToolsBadge,
35
+ GLOBAL_STYLES: () => GLOBAL_STYLES,
35
36
  HistoryPanel: () => HistoryPanel,
36
37
  InspectPanel: () => InspectPanel,
37
38
  ListView: () => ListView,
@@ -41,9 +42,11 @@ __export(index_exports, {
41
42
  SetupPanel: () => SetupPanel,
42
43
  exportProjectToZip: () => exportProjectToZip,
43
44
  formatTreeToMarkdown: () => formatTreeToMarkdown,
45
+ getFormattedTimestamp: () => getFormattedTimestamp,
44
46
  importProjectFromZip: () => importProjectFromZip,
45
47
  injectXMPMetadata: () => injectXMPMetadata,
46
48
  parsePromptFile: () => parsePromptFile,
49
+ useKeyboardNavigation: () => useKeyboardNavigation,
47
50
  useOnClickOutside: () => useOnClickOutside
48
51
  });
49
52
  module.exports = __toCommonJS(index_exports);
@@ -65,6 +68,42 @@ function useOnClickOutside(ref, handler) {
65
68
  }, [ref, handler]);
66
69
  }
67
70
 
71
+ // src/hooks/useKeyboardNavigation.ts
72
+ var import_react2 = require("react");
73
+ function useKeyboardNavigation(history, currentResult, setCurrentResult) {
74
+ (0, import_react2.useEffect)(() => {
75
+ const handleKeyDown = (e) => {
76
+ if (e.target instanceof HTMLTextAreaElement || e.target instanceof HTMLInputElement) return;
77
+ if (!currentResult || history.length === 0) return;
78
+ const currentIndex = history.findIndex((h) => h.id === currentResult.id);
79
+ if (e.key === "ArrowRight" && currentIndex < history.length - 1) {
80
+ setCurrentResult(history[currentIndex + 1]);
81
+ } else if (e.key === "ArrowLeft" && currentIndex > 0) {
82
+ setCurrentResult(history[currentIndex - 1]);
83
+ }
84
+ };
85
+ window.addEventListener("keydown", handleKeyDown);
86
+ return () => window.removeEventListener("keydown", handleKeyDown);
87
+ }, [currentResult, history, setCurrentResult]);
88
+ }
89
+
90
+ // src/lib/utils.ts
91
+ var getFormattedTimestamp = () => {
92
+ const d = /* @__PURE__ */ new Date();
93
+ const pad = (n) => n.toString().padStart(2, "0");
94
+ return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}_${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
95
+ };
96
+ var GLOBAL_STYLES = `
97
+ .dark-scrollbar::-webkit-scrollbar { width: 4px; height: 4px; }
98
+ .dark-scrollbar::-webkit-scrollbar-track { background: transparent; }
99
+ .dark-scrollbar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 10px; }
100
+ html, body, #root { margin: 0; padding: 0; width: 100%; height: 100%; background: #0e0e0e; font-family: 'Google Sans Text', sans-serif; overflow: hidden; }
101
+ @keyframes prompt-shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }
102
+ .prompt-loading { background: linear-gradient(90deg, rgba(255,255,255,0.02) 25%, rgba(255,255,255,0.05) 50%, rgba(255,255,255,0.02) 75%); background-size: 200% 100%; animation: prompt-shimmer 2s infinite linear; }
103
+ @keyframes dropdown-enter { from { opacity: 0; transform: scale(0.95) translateY(-5px); } to { opacity: 1; transform: scale(1) translateY(0); } }
104
+ .animate-dropdown { animation: dropdown-enter 0.1s ease-out forwards; }
105
+ `;
106
+
68
107
  // src/lib/metadata.ts
69
108
  var crcTable = (() => {
70
109
  let c;
@@ -381,7 +420,7 @@ var import_jsx_runtime3 = require("react/jsx-runtime");
381
420
  var SectionLabel = ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex items-center px-2", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[9px] font-bold text-white/30 uppercase tracking-widest text-nowrap", children }) });
382
421
 
383
422
  // src/components/CompactDropdown.tsx
384
- var import_react2 = require("react");
423
+ var import_react3 = require("react");
385
424
  var import_jsx_runtime4 = require("react/jsx-runtime");
386
425
  var CompactDropdown = ({
387
426
  value,
@@ -390,8 +429,8 @@ var CompactDropdown = ({
390
429
  onChange,
391
430
  className = ""
392
431
  }) => {
393
- const [isOpen, setIsOpen] = (0, import_react2.useState)(false);
394
- const ref = (0, import_react2.useRef)(null);
432
+ const [isOpen, setIsOpen] = (0, import_react3.useState)(false);
433
+ const ref = (0, import_react3.useRef)(null);
395
434
  useOnClickOutside(ref, () => setIsOpen(false));
396
435
  return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { ref, className: `relative shrink-0 ${className}`, children: [
397
436
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
@@ -423,7 +462,7 @@ var CompactDropdown = ({
423
462
  };
424
463
 
425
464
  // src/components/HistoryPanel.tsx
426
- var import_react3 = require("motion/react");
465
+ var import_react4 = require("motion/react");
427
466
  var import_jsx_runtime5 = require("react/jsx-runtime");
428
467
  var formatFriendlyTimestamp = (timestamp) => {
429
468
  const date = new Date(timestamp);
@@ -443,7 +482,7 @@ var HistoryPanel = ({ history, currentResultId, onSelect, onDelete }) => {
443
482
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "text-[10px] font-bold uppercase tracking-widest", children: "Keine Historie" })
444
483
  ] });
445
484
  }
446
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react3.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "absolute inset-0 p-4 flex flex-col gap-3 overflow-y-auto dark-scrollbar", children: history.map((gen) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
485
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react4.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "absolute inset-0 p-4 flex flex-col gap-3 overflow-y-auto dark-scrollbar", children: history.map((gen) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
447
486
  "div",
448
487
  {
449
488
  className: `flex gap-3 p-2 bg-white/5 border rounded-2xl cursor-pointer group transition-all relative ${currentResultId === gen.id ? "border-white/40 bg-white/10 shadow-lg" : "border-white/5 hover:border-white/20"}`,
@@ -472,7 +511,7 @@ var HistoryPanel = ({ history, currentResultId, onSelect, onDelete }) => {
472
511
  };
473
512
 
474
513
  // src/components/InspectPanel.tsx
475
- var import_react4 = require("motion/react");
514
+ var import_react5 = require("motion/react");
476
515
  var import_jsx_runtime6 = require("react/jsx-runtime");
477
516
  var InspectPanel = ({ currentResult, history, onSelect, workspaceTags, onTagToggle }) => {
478
517
  if (!currentResult) {
@@ -482,7 +521,7 @@ var InspectPanel = ({ currentResult, history, onSelect, workspaceTags, onTagTogg
482
521
  ] });
483
522
  }
484
523
  const fullDateStr = new Date(currentResult.timestamp).toLocaleString([], { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit" });
485
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react4.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "absolute inset-0 flex flex-col overflow-hidden", children: [
524
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react5.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "absolute inset-0 flex flex-col overflow-hidden", children: [
486
525
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex-shrink-0 border-b border-white/5 bg-black/20 py-3 px-4", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex gap-2 overflow-x-auto no-scrollbar pb-1", children: history.map((gen) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { onClick: () => onSelect(gen), className: `w-10 h-10 rounded-lg overflow-hidden border shrink-0 transition-all ${currentResult.id === gen.id ? "border-white scale-105 shadow-lg" : "border-white/5 opacity-40 hover:opacity-100"}`, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("img", { src: gen.base64 ? gen.base64.startsWith("data:") ? gen.base64 : `data:image/png;base64,${gen.base64}` : "", className: "w-full h-full object-cover", alt: "Thumbnail" }) }, gen.id)) }) }),
487
526
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex-1 overflow-y-auto p-4 flex flex-col gap-6 dark-scrollbar pb-10", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-col gap-4", children: [
488
527
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(SectionLabel, { children: "Vorschau" }),
@@ -524,13 +563,13 @@ var InspectPanel = ({ currentResult, history, onSelect, workspaceTags, onTagTogg
524
563
  };
525
564
 
526
565
  // src/components/SetupPanel.tsx
527
- var import_react5 = require("react");
528
- var import_react6 = require("motion/react");
566
+ var import_react6 = require("react");
567
+ var import_react7 = require("motion/react");
529
568
  var import_jsx_runtime7 = require("react/jsx-runtime");
530
569
  var SetupPanel = ({ onWorkspaceImport, onProjectExport, onProjectImport, projectActionState }) => {
531
- const workspaceInputRef = (0, import_react5.useRef)(null);
532
- const projectInputRef = (0, import_react5.useRef)(null);
533
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react6.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "absolute inset-0 p-6 flex flex-col gap-10", children: [
570
+ const workspaceInputRef = (0, import_react6.useRef)(null);
571
+ const projectInputRef = (0, import_react6.useRef)(null);
572
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react7.motion.div, { initial: { opacity: 0 }, animate: { opacity: 1 }, className: "absolute inset-0 p-6 flex flex-col gap-10", children: [
534
573
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-4", children: [
535
574
  /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "flex flex-col gap-1", children: [
536
575
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(SectionLabel, { children: "Workspace Management" }),
@@ -573,7 +612,7 @@ var SetupPanel = ({ onWorkspaceImport, onProjectExport, onProjectImport, project
573
612
  };
574
613
 
575
614
  // src/components/MediaLibrary.tsx
576
- var import_react7 = require("motion/react");
615
+ var import_react8 = require("motion/react");
577
616
  var import_jsx_runtime8 = require("react/jsx-runtime");
578
617
  var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, onBatchDownload, onGenerateReference }) => {
579
618
  const selectedCount = items.filter((i) => i.selectedForExport).length;
@@ -592,7 +631,7 @@ var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, on
592
631
  "Laden"
593
632
  ] })
594
633
  ] }),
595
- selectedCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react7.motion.div, { initial: { opacity: 0, y: -10 }, animate: { opacity: 1, y: 0 }, className: "flex items-center justify-between bg-blue-500/10 border border-blue-500/20 p-2 rounded-xl", children: [
634
+ selectedCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react8.motion.div, { initial: { opacity: 0, y: -10 }, animate: { opacity: 1, y: 0 }, className: "flex items-center justify-between bg-blue-500/10 border border-blue-500/20 p-2 rounded-xl", children: [
596
635
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("span", { className: "text-[9px] font-bold uppercase text-blue-400 ml-1", children: [
597
636
  selectedCount,
598
637
  " ausgew\xE4hlt"
@@ -609,7 +648,7 @@ var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, on
609
648
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-[12px] font-bold uppercase tracking-widest", children: "Keine Medien" }),
610
649
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-[10px] italic", children: "Importiere Assets aus deinem Projekt." })
611
650
  ] })
612
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "grid grid-cols-2 gap-3 pb-10", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react7.motion.div, { initial: { opacity: 0, scale: 0.9 }, animate: { opacity: 1, scale: 1 }, className: `relative aspect-square group/item rounded-xl overflow-hidden border transition-all bg-white/5 cursor-pointer shadow-lg ${item.selectedForExport ? "border-blue-500 shadow-[0_0_15px_rgba(59,130,246,0.2)]" : "border-white/10 opacity-70 hover:opacity-100"}`, onClick: () => onSelect?.(item), children: [
651
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "grid grid-cols-2 gap-3 pb-10", children: items.map((item) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react8.motion.div, { initial: { opacity: 0, scale: 0.9 }, animate: { opacity: 1, scale: 1 }, className: `relative aspect-square group/item rounded-xl overflow-hidden border transition-all bg-white/5 cursor-pointer shadow-lg ${item.selectedForExport ? "border-blue-500 shadow-[0_0_15px_rgba(59,130,246,0.2)]" : "border-white/10 opacity-70 hover:opacity-100"}`, onClick: () => onSelect?.(item), children: [
613
652
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("img", { src: item.base64 ? item.base64.startsWith("data:") ? item.base64 : `data:image/png;base64,${item.base64}` : "", className: "w-full h-full object-cover transition-transform duration-500 group-hover/item:scale-110", alt: item.prompt }),
614
653
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", className: `absolute top-2 left-2 w-5 h-5 rounded-md border flex items-center justify-center transition-all z-30 pointer-events-auto ${item.selectedForExport ? "bg-blue-500 border-blue-400" : "bg-black/60 border-white/20"}`, onClick: (e) => {
615
654
  e.stopPropagation();
@@ -634,10 +673,10 @@ var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, on
634
673
  };
635
674
 
636
675
  // src/components/ListView.tsx
637
- var import_react8 = require("react");
676
+ var import_react9 = require("react");
638
677
  var import_jsx_runtime9 = require("react/jsx-runtime");
639
678
  var ListNode = ({ node, depth, onNodeChange, onAddChild, onDeleteNode, onMoveNode, onIndentNode, onOutdentNode, onAddSibling, isActive, isInPath, onFocus, onGenerate, onGenerateBranch, onGenerateSubtree, isGenerating, isCollapsed, toggleCollapse, renderNode, children }) => {
640
- const inputRef = (0, import_react8.useRef)(null);
679
+ const inputRef = (0, import_react9.useRef)(null);
641
680
  const hasChildren = children && children.length > 0;
642
681
  const handleKeyDown = (e) => {
643
682
  if (e.key === "Tab") {
@@ -665,7 +704,7 @@ var ListNode = ({ node, depth, onNodeChange, onAddChild, onDeleteNode, onMoveNod
665
704
  onMoveNode(node.id, "down");
666
705
  }
667
706
  };
668
- (0, import_react8.useEffect)(() => {
707
+ (0, import_react9.useEffect)(() => {
669
708
  if (isActive && inputRef.current) inputRef.current.focus();
670
709
  }, [isActive]);
671
710
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex flex-col w-full", children: [
@@ -699,7 +738,7 @@ var ListNode = ({ node, depth, onNodeChange, onAddChild, onDeleteNode, onMoveNod
699
738
  ] });
700
739
  };
701
740
  function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMoveNode, onIndentNode, onOutdentNode, onAddSibling, focusedNodeId, onFocus, activePath, onGenerate, onGenerateBranch, onGenerateSubtree, isGeneratingNodeId }) {
702
- const [collapsed, setCollapsed] = (0, import_react8.useState)(/* @__PURE__ */ new Set());
741
+ const [collapsed, setCollapsed] = (0, import_react9.useState)(/* @__PURE__ */ new Set());
703
742
  const toggleCollapse = (id) => {
704
743
  setCollapsed((prev) => {
705
744
  const next = new Set(prev);
@@ -729,6 +768,7 @@ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMove
729
768
  0 && (module.exports = {
730
769
  CompactDropdown,
731
770
  FaToolsBadge,
771
+ GLOBAL_STYLES,
732
772
  HistoryPanel,
733
773
  InspectPanel,
734
774
  ListView,
@@ -738,8 +778,10 @@ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMove
738
778
  SetupPanel,
739
779
  exportProjectToZip,
740
780
  formatTreeToMarkdown,
781
+ getFormattedTimestamp,
741
782
  importProjectFromZip,
742
783
  injectXMPMetadata,
743
784
  parsePromptFile,
785
+ useKeyboardNavigation,
744
786
  useOnClickOutside
745
787
  });
package/dist/index.mjs CHANGED
@@ -15,6 +15,42 @@ function useOnClickOutside(ref, handler) {
15
15
  }, [ref, handler]);
16
16
  }
17
17
 
18
+ // src/hooks/useKeyboardNavigation.ts
19
+ import { useEffect as useEffect2 } from "react";
20
+ function useKeyboardNavigation(history, currentResult, setCurrentResult) {
21
+ useEffect2(() => {
22
+ const handleKeyDown = (e) => {
23
+ if (e.target instanceof HTMLTextAreaElement || e.target instanceof HTMLInputElement) return;
24
+ if (!currentResult || history.length === 0) return;
25
+ const currentIndex = history.findIndex((h) => h.id === currentResult.id);
26
+ if (e.key === "ArrowRight" && currentIndex < history.length - 1) {
27
+ setCurrentResult(history[currentIndex + 1]);
28
+ } else if (e.key === "ArrowLeft" && currentIndex > 0) {
29
+ setCurrentResult(history[currentIndex - 1]);
30
+ }
31
+ };
32
+ window.addEventListener("keydown", handleKeyDown);
33
+ return () => window.removeEventListener("keydown", handleKeyDown);
34
+ }, [currentResult, history, setCurrentResult]);
35
+ }
36
+
37
+ // src/lib/utils.ts
38
+ var getFormattedTimestamp = () => {
39
+ const d = /* @__PURE__ */ new Date();
40
+ const pad = (n) => n.toString().padStart(2, "0");
41
+ return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}_${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
42
+ };
43
+ var GLOBAL_STYLES = `
44
+ .dark-scrollbar::-webkit-scrollbar { width: 4px; height: 4px; }
45
+ .dark-scrollbar::-webkit-scrollbar-track { background: transparent; }
46
+ .dark-scrollbar::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 10px; }
47
+ html, body, #root { margin: 0; padding: 0; width: 100%; height: 100%; background: #0e0e0e; font-family: 'Google Sans Text', sans-serif; overflow: hidden; }
48
+ @keyframes prompt-shimmer { 0% { background-position: -200% 0; } 100% { background-position: 200% 0; } }
49
+ .prompt-loading { background: linear-gradient(90deg, rgba(255,255,255,0.02) 25%, rgba(255,255,255,0.05) 50%, rgba(255,255,255,0.02) 75%); background-size: 200% 100%; animation: prompt-shimmer 2s infinite linear; }
50
+ @keyframes dropdown-enter { from { opacity: 0; transform: scale(0.95) translateY(-5px); } to { opacity: 1; transform: scale(1) translateY(0); } }
51
+ .animate-dropdown { animation: dropdown-enter 0.1s ease-out forwards; }
52
+ `;
53
+
18
54
  // src/lib/metadata.ts
19
55
  var crcTable = (() => {
20
56
  let c;
@@ -584,7 +620,7 @@ var MediaLibrary = ({ items, onImport, onDelete, onSelect, onToggleSelection, on
584
620
  };
585
621
 
586
622
  // src/components/ListView.tsx
587
- import { useEffect as useEffect2, useRef as useRef3, useState as useState2 } from "react";
623
+ import { useEffect as useEffect3, useRef as useRef3, useState as useState2 } from "react";
588
624
  import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
589
625
  var ListNode = ({ node, depth, onNodeChange, onAddChild, onDeleteNode, onMoveNode, onIndentNode, onOutdentNode, onAddSibling, isActive, isInPath, onFocus, onGenerate, onGenerateBranch, onGenerateSubtree, isGenerating, isCollapsed, toggleCollapse, renderNode, children }) => {
590
626
  const inputRef = useRef3(null);
@@ -615,7 +651,7 @@ var ListNode = ({ node, depth, onNodeChange, onAddChild, onDeleteNode, onMoveNod
615
651
  onMoveNode(node.id, "down");
616
652
  }
617
653
  };
618
- useEffect2(() => {
654
+ useEffect3(() => {
619
655
  if (isActive && inputRef.current) inputRef.current.focus();
620
656
  }, [isActive]);
621
657
  return /* @__PURE__ */ jsxs7("div", { className: "flex flex-col w-full", children: [
@@ -678,6 +714,7 @@ function ListView({ nodes, edges, onNodeChange, onAddChild, onDeleteNode, onMove
678
714
  export {
679
715
  CompactDropdown,
680
716
  FaToolsBadge,
717
+ GLOBAL_STYLES,
681
718
  HistoryPanel,
682
719
  InspectPanel,
683
720
  ListView,
@@ -687,8 +724,10 @@ export {
687
724
  SetupPanel,
688
725
  exportProjectToZip,
689
726
  formatTreeToMarkdown,
727
+ getFormattedTimestamp,
690
728
  importProjectFromZip,
691
729
  injectXMPMetadata,
692
730
  parsePromptFile,
731
+ useKeyboardNavigation,
693
732
  useOnClickOutside
694
733
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rslsp1/fa-app-tools",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Shared tools and hooks for Fine Art flow apps",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",