Skip to content

Commit cda8c45

Browse files
committed
feat(client): add logs modal and search hotkeys, increase timeouts
1 parent 73c4eeb commit cda8c45

7 files changed

Lines changed: 66 additions & 7 deletions

File tree

client/src/App.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import api from "./utils/api";
1414
import storage from "./utils/storage";
1515
import BookView from "./components/BookView";
1616
import WelcomeModal from "./components/WelcomeModal";
17+
import LogsModal from "./components/LogsModal";
1718

1819
const useMemoryRouter =
1920
import.meta.env.VITE_REACT_APP_USE_MEMORY_ROUTER === "true";
@@ -25,6 +26,7 @@ function App() {
2526
const [isLoading, setIsLoading] = useState(true);
2627
const [triggerLogout, setTriggerLogout] = useState(false);
2728
const [showWelcomeModal, setShowWelcomeModal] = useState(false);
29+
const [showLogsModal, setShowLogsModal] = useState(false);
2830
const [sessionExpired, setSessionExpired] = useState(false);
2931

3032
useEffect(() => {
@@ -36,6 +38,15 @@ function App() {
3638
setTriggerLogout(true);
3739
});
3840
}
41+
42+
// Keyboard shortcut Ctrl+Alt+D to open logs modal
43+
const handleKeyDown = (e: KeyboardEvent) => {
44+
if ((e.ctrlKey || e.metaKey) && e.altKey && e.key === 'd') {
45+
setShowLogsModal(true);
46+
}
47+
};
48+
window.addEventListener('keydown', handleKeyDown);
49+
return () => window.removeEventListener('keydown', handleKeyDown);
3950
}, []);
4051

4152
useEffect(() => {
@@ -178,6 +189,10 @@ function App() {
178189
onClose={handleWelcomeClose}
179190
/>
180191
)}
192+
<LogsModal
193+
isOpen={showLogsModal}
194+
onClose={() => setShowLogsModal(false)}
195+
/>
181196
</div>
182197
</Router>
183198
);

client/src/components/Dashboard.tsx

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {useEffect, useState} from 'react';
1+
import React, {useEffect, useRef, useState} from 'react';
22
import {useNavigate} from 'react-router-dom';
33
import { useTranslation } from 'react-i18next';
44
import api from '../utils/api';
@@ -23,18 +23,36 @@ function Dashboard({onLogout, triggerLogout, setTriggerLogout}: DashboardProps)
2323
const [, setCurrentBook] = useState<BookShelfEntity | null>(null);
2424
const [filterStatus, setFilterStatus] = useState(-1)
2525
const [filteredBooks, setFilteredBooks] = useState<BookShelfEntity[]>([]);
26+
const [searchQuery, setSearchQuery] = useState('');
27+
const searchInputRef = useRef<HTMLInputElement>(null);
2628

2729
useEffect(() => {
2830
loadBookshelf();
31+
32+
const handleKeyDown = (e: KeyboardEvent) => {
33+
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
34+
e.preventDefault();
35+
searchInputRef.current?.focus();
36+
}
37+
};
38+
window.addEventListener('keydown', handleKeyDown);
39+
return () => window.removeEventListener('keydown', handleKeyDown);
2940
}, []);
3041

3142
useEffect(() => {
3243
if (books.length === 0) return;
3344
let _filterStatus = filterStatus === -1 ? [1,2] : [filterStatus];
34-
setFilteredBooks(
35-
books.filter(book => _filterStatus.includes(+book.status))
36-
);
37-
}, [filterStatus, books])
45+
let result = books.filter(book => _filterStatus.includes(+book.status));
46+
if (searchQuery.trim()) {
47+
const q = searchQuery.toLowerCase();
48+
result = result.filter(book =>
49+
book.book?.name?.toLowerCase().includes(q) ||
50+
book.book?.authorsAsString?.toLowerCase().includes(q) ||
51+
book.abook?.narratorAsString?.toLowerCase().includes(q)
52+
);
53+
}
54+
setFilteredBooks(result);
55+
}, [filterStatus, books, searchQuery])
3856

3957
const loadBookshelf = async () => {
4058
try {
@@ -88,6 +106,27 @@ function Dashboard({onLogout, triggerLogout, setTriggerLogout}: DashboardProps)
88106
</div>
89107
) : (
90108
<>
109+
<div className="relative mb-4">
110+
<svg className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
111+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-4.35-4.35M17 11A6 6 0 1 1 5 11a6 6 0 0 1 12 0z" />
112+
</svg>
113+
<input
114+
ref={searchInputRef}
115+
type="text"
116+
value={searchQuery}
117+
onChange={e => setSearchQuery(e.target.value)}
118+
placeholder={t('dashboard.search')}
119+
className="w-full bg-gray-900 border border-gray-700 text-white placeholder-gray-500 rounded-lg pl-10 pr-10 py-2 text-sm focus:outline-none focus:border-gray-500"
120+
/>
121+
{searchQuery && (
122+
<button
123+
onClick={() => setSearchQuery('')}
124+
className="absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 hover:text-white"
125+
>
126+
×
127+
</button>
128+
)}
129+
</div>
91130

92131
<div className="flex flex-wrap gap-3 mb-6">
93132
{ filterStatus !== -1 && (

client/src/types/window.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ declare global {
2222
};
2323
electronLogs?: {
2424
openLogsFolder: () => Promise<void>;
25+
onOpenLogsModal: (callback: () => void) => void;
2526
};
2627
electronWindow?: {
2728
setAlwaysOnTop: (alwaysOnTop: boolean) => Promise<void>;

client/src/utils/api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ declare global {
1010
const axiosApi = axios.create({
1111
baseURL: `${import.meta.env.VITE_BACKEND_API_URL}/api`, // o la tua baseURL reale
1212
withCredentials: true,
13-
timeout: 15000
13+
timeout: 30000
1414
});
1515

1616
axiosApi.interceptors.request.use(async (config) => {

server/storytelApi.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class StorytelClient {
3535
validateStatus: function (status) {
3636
return status < 400;
3737
},
38-
timeout: 15000,
38+
timeout: 30000,
3939
params: {
4040
version: "25.38.0",
4141
},

src/main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ async function initialize(): Promise<void> {
4040

4141
ipcManager = new IpcManager(serverManager, trayManager, windowManager);
4242
ipcManager.setupHandlers();
43+
4344
// Initialize auto-updater
4445
updaterManager = new UpdaterManager(mainWindow, isDev);
4546
updaterManager.initialize();

src/preload.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ contextBridge.exposeInMainWorld('trayControls', {
4444
// Logs API
4545
contextBridge.exposeInMainWorld('electronLogs', {
4646
openLogsFolder: (): Promise<void> => ipcRenderer.invoke('open-logs-folder'),
47+
onOpenLogsModal: (callback: () => void): void => {
48+
ipcRenderer.on('open-logs-modal', callback);
49+
},
4750
});
4851

4952
// Electron API

0 commit comments

Comments
 (0)