release: publish Multi-Debrid Downloader v2.0.14
Add live English and German localization, queue availability and metadata resolution, responsive package controls, polished navigation and drag interactions, clearer history and account states, and a rebuilt public README. Harden Windows packaging with verified icons and version metadata, archive inspection, and expanded release tests.
This commit is contained in:
@@ -1,38 +1,71 @@
|
||||
import type { ReactElement, ReactNode } from "react";
|
||||
import { useEffect, useLayoutEffect, useRef, type ReactElement, type ReactNode } from "react";
|
||||
import appIconUrl from "../../../assets/app_icon.png";
|
||||
import { Icon } from "../ui/Icon";
|
||||
import { buildMainNavigation, type MainView } from "./shell-model";
|
||||
|
||||
import { buildMainNavigation, type MainView } from "./shell-model";
|
||||
|
||||
export interface AppHeaderProps {
|
||||
activeView: MainView;
|
||||
onViewChange: (view: MainView) => void;
|
||||
actions: ReactNode;
|
||||
activeView: MainView;
|
||||
onViewChange: (view: MainView) => void;
|
||||
actions: ReactNode;
|
||||
}
|
||||
|
||||
const useHeaderLayoutEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
||||
|
||||
export function AppHeader({ activeView, onViewChange, actions }: AppHeaderProps): ReactElement {
|
||||
const navigation = buildMainNavigation(activeView);
|
||||
const navigationRef = useRef<HTMLElement>(null);
|
||||
|
||||
return (
|
||||
<header className="md-shell-header" data-ui-region="header">
|
||||
<div className="md-shell-brand">Multi-Debrid-Downloader</div>
|
||||
<nav aria-label="Hauptnavigation" className="md-shell-navigation" role="tablist">
|
||||
{navigation.map((item) => (
|
||||
<button
|
||||
aria-current={item.active ? "page" : undefined}
|
||||
aria-selected={item.active}
|
||||
useHeaderLayoutEffect(() => {
|
||||
const element = navigationRef.current;
|
||||
if (!element) return;
|
||||
let frame = 0;
|
||||
const sync = (): void => {
|
||||
cancelAnimationFrame(frame);
|
||||
frame = requestAnimationFrame(() => {
|
||||
const activeItem = element.querySelector<HTMLElement>(`.md-shell-navigation-item[data-main-view="${activeView}"]`);
|
||||
if (!activeItem) return;
|
||||
const navigationRect = element.getBoundingClientRect();
|
||||
const itemRect = activeItem.getBoundingClientRect();
|
||||
element.style.setProperty("--md-navigation-active-x", `${itemRect.left - navigationRect.left}px`);
|
||||
element.style.setProperty("--md-navigation-active-y", `${itemRect.top - navigationRect.top}px`);
|
||||
element.style.setProperty("--md-navigation-active-width", `${itemRect.width}px`);
|
||||
element.style.setProperty("--md-navigation-active-height", `${itemRect.height}px`);
|
||||
element.classList.add("has-active-indicator");
|
||||
});
|
||||
};
|
||||
const observer = new ResizeObserver(sync);
|
||||
observer.observe(element);
|
||||
element.querySelectorAll<HTMLElement>(".md-shell-navigation-item").forEach((item) => observer.observe(item));
|
||||
sync();
|
||||
return () => {
|
||||
cancelAnimationFrame(frame);
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [activeView]);
|
||||
|
||||
return (
|
||||
<header className="md-shell-header" data-ui-region="header">
|
||||
<div className="md-shell-brand"><img alt="" className="md-shell-brand-mark" src={appIconUrl} /><span>Multi-Debrid Downloader</span></div>
|
||||
<nav aria-label="Hauptnavigation" className="md-shell-navigation" ref={navigationRef} role="tablist">
|
||||
{navigation.map((item) => (
|
||||
<button
|
||||
aria-current={item.active ? "page" : undefined}
|
||||
aria-selected={item.active}
|
||||
className={`md-shell-navigation-item${item.active ? " is-active" : ""}`}
|
||||
data-visual-active-view={item.active ? item.id : undefined}
|
||||
key={item.id}
|
||||
onClick={() => onViewChange(item.id)}
|
||||
role="tab"
|
||||
title={item.label}
|
||||
type="button"
|
||||
>
|
||||
<Icon name={item.icon} size={18} />
|
||||
<span>{item.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
{actions ? <div aria-label="Globale Aktionen" className="md-shell-header-actions" role="group">{actions}</div> : null}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
data-main-view={item.id}
|
||||
data-visual-active-view={item.active ? item.id : undefined}
|
||||
key={item.id}
|
||||
onClick={() => onViewChange(item.id)}
|
||||
role="tab"
|
||||
title={item.label}
|
||||
type="button"
|
||||
>
|
||||
<Icon name={item.icon} size={18} />
|
||||
<span>{item.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
{actions ? <div aria-label="Globale Aktionen" className="md-shell-header-actions" role="group">{actions}</div> : null}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ export function UpdateExperience({
|
||||
{releaseNotes.trim() ? (
|
||||
<details className="md-update-release-notes">
|
||||
<summary>Changelog anzeigen</summary>
|
||||
<pre>{releaseNotes}</pre>
|
||||
<pre data-i18n-ignore="true">{releaseNotes}</pre>
|
||||
</details>
|
||||
) : null}
|
||||
</>
|
||||
|
||||
+860
-775
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user