Improve startup responsiveness and admin prompt motion
CI / Windows baseline (push) Has been cancelled

This commit is contained in:
2026-07-22 17:50:12 +03:00
parent 90ec4ca086
commit 9c987df6e9
11 changed files with 597 additions and 183 deletions
+84 -22
View File
@@ -220,6 +220,11 @@ export function App() {
const [serviceVisualState, setServiceVisualState] =
useState<ServiceVisualState>(null);
const serviceVisualTimerRef = useRef<number | null>(null);
const [isAdminPromptOpen, setIsAdminPromptOpen] = useState(false);
const [isAdminPromptHintVisible, setIsAdminPromptHintVisible] =
useState(false);
const adminPromptSeenRef = useRef(false);
const adminPromptTimerRef = useRef<number | null>(null);
const proxyfier = useMemo(
() => components.find((component) => component.id === "proxyfier"),
@@ -278,11 +283,32 @@ export function App() {
void refresh();
}, []);
useEffect(() => {
if (!hasAdminPrompt || adminPromptSeenRef.current) return undefined;
adminPromptSeenRef.current = true;
setIsAdminPromptHintVisible(true);
adminPromptTimerRef.current = window.setTimeout(() => {
setIsAdminPromptHintVisible(false);
adminPromptTimerRef.current = null;
}, 4600);
return () => {
if (adminPromptTimerRef.current !== null) {
window.clearTimeout(adminPromptTimerRef.current);
adminPromptTimerRef.current = null;
}
};
}, [hasAdminPrompt]);
useEffect(() => {
return () => {
if (serviceVisualTimerRef.current !== null) {
window.clearTimeout(serviceVisualTimerRef.current);
}
if (adminPromptTimerRef.current !== null) {
window.clearTimeout(adminPromptTimerRef.current);
}
};
}, []);
@@ -1017,29 +1043,64 @@ export function App() {
}, 700);
}
function toggleAdminPrompt() {
if (adminPromptTimerRef.current !== null) {
window.clearTimeout(adminPromptTimerRef.current);
adminPromptTimerRef.current = null;
}
setIsAdminPromptHintVisible(false);
setIsAdminPromptOpen((open) => !open);
}
function renderAdminPrompt() {
if (!hasAdminPrompt) return null;
return (
<aside className="admin-prompt" aria-label="Права администратора">
<span className="admin-prompt-icon" aria-hidden="true">
<ShieldAlert size={15} strokeWidth={1.9} />
</span>
<div className="admin-prompt-copy">
<strong>Права администратора</strong>
<span>{adminStatus?.message}</span>
</div>
<Button
<aside
className={`admin-prompt ${isAdminPromptOpen ? "is-expanded" : "is-collapsed"} ${isAdminPromptHintVisible ? "has-hint" : ""}`}
aria-label="Права администратора"
>
<button
type="button"
variant="primary"
size="sm"
className="admin-prompt-action"
onClick={() => void restartApplicationAsAdmin()}
loading={isRestartingAsAdmin}
loadingLabel="Открываю UAC"
className="admin-prompt-toggle"
onClick={toggleAdminPrompt}
aria-expanded={isAdminPromptOpen}
aria-controls="admin-prompt-details"
aria-label={
isAdminPromptOpen
? "Свернуть информацию о правах администратора"
: "Показать, зачем нужны права администратора"
}
>
Перезапустить с правами
</Button>
<ShieldAlert size={18} strokeWidth={1.9} aria-hidden="true" />
</button>
{isAdminPromptHintVisible && !isAdminPromptOpen ? (
<span className="admin-prompt-hint" role="status">
Для удобной работы нужны права администратора.
</span>
) : null}
<div
className="admin-prompt-details"
id="admin-prompt-details"
aria-hidden={!isAdminPromptOpen}
>
<div className="admin-prompt-copy">
<strong>Нужны права администратора</strong>
<span>Для управления ProxiFyre и правилами Windows.</span>
</div>
<Button
type="button"
variant="primary"
size="sm"
className="admin-prompt-action"
onClick={() => void restartApplicationAsAdmin()}
loading={isRestartingAsAdmin}
loadingLabel="Открываю UAC"
tabIndex={isAdminPromptOpen ? undefined : -1}
>
Перезапустить
</Button>
</div>
</aside>
);
}
@@ -1690,6 +1751,11 @@ export function App() {
align={isVertical || index >= segments.length - 2 ? "end" : "start"}
aria-label={`${segment.label}: ${segment.value}. ${segment.details.join(". ")}`}
key={segment.id}
style={
isVertical
? ({ animationDelay: `${index * 280}ms` } as CSSProperties)
: undefined
}
>
<span className="route-chain-dot" aria-hidden="true" />
<span>{segment.label}</span>
@@ -1713,11 +1779,7 @@ export function App() {
return (
<main
className={[
"simple-shell",
hasUnappliedChanges ? "has-change-dock" : "",
hasAdminPrompt ? "has-admin-prompt" : "",
]
className={["simple-shell", hasUnappliedChanges ? "has-change-dock" : ""]
.filter(Boolean)
.join(" ")}
style={shellStyle}