import "./index.css";
import { StrictMode, useLayoutEffect, useRef, useState, useEffect, useMemo } from "react";
import { createRoot } from "react-dom/client";
import { useParams, Navigate, BrowserRouter, Link, Outlet, Route, Routes } from "react-router";
import { useShallow } from "zustand/shallow";
import { marked } from "marked";
import { apiInstance, resourcesInstance } from "./api.js";
import { useStore } from "./store.js";
import {
ArrowBackIcon,
ArrowForwardIcon,
MenuBookIcon,
MyLocationIcon,
TitleIcon,
WidthIcon,
EllipsisIcon,
CloseIcon,
JustifyTextIcon,
TextIncreaseIcon,
TextDecreaseIcon,
VRuleIcon,
} from "./icons/Icons";
createRoot(document.getElementById("root")).render(
}>
} />
} />
,
);
function Layout() {
const params = useParams();
const topic = useStore((state) => params.topicId && state.topics.byId[params.topicId]);
const config = useStore((state) => state.config);
return (
{config.displayTitle && (
{topic ? `${topic.index + 1}: ${topic.title}` : "Конспект за Държавен Изпит"}
)}
);
}
const SUBJECTS = [
{ id: 0, name: "ВЪТРЕШНИ БОЛЕСТИ" },
{ id: 1, name: "ФАРМАКОЛОГИЯ" },
];
export function TopicListView() {
const itemRefs = useRef({});
const topics = useStore(
useShallow((state) => state.topics.allIds.map((id) => state.topics.byId[id])),
);
const selectedTopic = useStore((state) => state.selectedTopic);
const selectTopic = useStore((state) => state.selectTopic);
const selectedSubjectIndex = useStore((state) => state.subject);
const setSelectedSubjectIndex = useStore((state) => state.selectSubject);
const config = useStore((state) => state.config);
const changeConfig = useStore((state) => state.changeConfig);
const [isSelectingSubject, setIsSelectingSubject] = useState(false);
useLayoutEffect(() => {
if (selectedTopic && selectedTopic.id !== null) {
itemRefs.current?.[Math.max(selectedTopic.index - 3, 0)].scrollIntoView();
}
}, [selectedTopic]);
return (
<>
{topics.map((topic, i) => (
{
itemRefs.current[i] = node;
}}
to={`/${topic.id}`}
onClick={() => selectTopic(topic.id)}
className={`flex px-2 py-1 rounded-md cursor-pointer border-l-4 ${topic.id === selectedTopic?.id ? "bg-blue-100 border-blue-500" : "border-transparent hover:bg-gray-100"}`}
>
{i + 1}
{topic.title}
))}
changeConfig({ displayTitle: !config.displayTitle })}
>
changeConfig({ wrapTopicTitles: !config.wrapTopicTitles })}
>
{selectedTopic !== null && (
{
itemRefs.current?.[Math.max(selectedTopic.index - 3, 0)].scrollIntoView({
behavior: "smooth",
});
}}
>
)}
setIsSelectingSubject(true)}
>
{SUBJECTS[selectedSubjectIndex].name}
{isSelectingSubject && (
<>
setIsSelectingSubject(false)}
>
{SUBJECTS.map((subject) => (
{
setSelectedSubjectIndex(subject.id);
setIsSelectingSubject(false);
}}
>
{subject.name}
))}
>
)}
{selectedTopic !== null && (
Продължи четенето:
{selectedTopic.index + 1}. {selectedTopic.title}
)}
>
);
}
export function FileReader() {
const params = useParams();
const topicsById = useStore((state) => state.topics.byId);
const selectTopic = useStore((state) => state.selectTopic);
const topic = topicsById[params.topicId];
if (!topic) {
return ;
}
selectTopic(topic.id);
return ;
}
export function Reader({ topic }) {
const config = useStore((state) => state.config);
const changeConfig = useStore((state) => state.changeConfig);
const selectedVersion = useStore((state) => state.topicVersions[topic.id] ?? 0);
const selectVersion = useStore((state) => state.selectTopicVersion);
const getTopicAtIndex = useStore((state) => state.getTopicAtIndex);
const prevTopic = getTopicAtIndex(topic.index - 1);
const nextTopic = getTopicAtIndex(topic.index + 1);
const [isSelectingVersion, setIsSelectingVersion] = useState(false);
return (
<>
{config.contentZoomLevel}%
changeConfig({ contentZoomLevel: Math.max(50, config.contentZoomLevel - 10) })
}
>
{
changeConfig({ contentZoomLevel: Math.min(150, config.contentZoomLevel + 10) });
}}
>
changeConfig({ displayTitle: !config.displayTitle })}
>
changeConfig({ justifyText: !config.justifyText })}
>
{window.innerWidth > 576 && (
changeConfig({ narrowMode: !config.narrowMode })}
>
)}
{topic.files.length > 1 && (
setIsSelectingVersion(true)}
>
Версия {selectedVersion + 1}
{isSelectingVersion && (
setIsSelectingVersion(false)}
>
)}
)}
{isSelectingVersion && (
{topic.files.map((file, vIndex) => (
{
selectVersion(topic.id, vIndex);
setIsSelectingVersion(false);
}}
>
Версия {vIndex + 1}
))}
)}
{topic.isFirst ? (
) : (
{prevTopic.index + 1}: {prevTopic.title}
)}
{topic.isLast ? (
) : (
{nextTopic.index + 1}: {nextTopic.title}
)}
>
);
}
export function PDFViewer({ file, compact, zoomFactor, justifyText }) {
const iframeRef = useRef(null);
const [content, setContent] = useState(null);
const htmlContent = useMemo(() => {
const fileContent = `
${content}
`;
return fileContent;
}, [content, compact, justifyText, zoomFactor]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
const contentZoomLevel = useStore((state) => state.config.contentZoomLevel);
useEffect(() => {
if (iframeRef.current && iframeRef.current.contentDocument) {
iframeRef.current.contentDocument.body.style.zoom = `${contentZoomLevel}%`;
}
}, [contentZoomLevel]);
useEffect(() => {
const fetchFile = async () => {
try {
setIsLoading(true);
const mdPath = `/files_md/${file.filename}`;
const response = await fetch(mdPath);
if (!response.ok) {
throw new Error(`Failed to load file: ${response.status}`);
}
let fileContent = await response.text();
if (fileContent === "") {
fileContent = "**No Data!**";
}
fileContent = marked.parse(fileContent);
setContent(fileContent);
setError(null);
} catch (err) {
console.error("Error loading file:", err);
setError(err.message);
} finally {
setIsLoading(false);
}
};
fetchFile();
}, [file]);
if (error) {
return Error: {error}
;
}
if (isLoading) {
return ;
}
return (
);
}
const DelayedLoader = ({
delayMs = 2000,
className = "p-4 flex justify-center items-center h-40",
text = "Loading...",
}) => {
const [showLoader, setShowLoader] = useState(false);
useEffect(() => {
// Set a timeout to show the loader after the specified delay
const timer = setTimeout(() => {
setShowLoader(true);
}, delayMs);
// Clear the timeout on unmount
return () => clearTimeout(timer);
}, []); // Empty dependency array - only run on mount
// Only render if the delay has passed
if (showLoader) {
return (
);
}
// Return null before delay threshold
return null;
};