Improve startup responsiveness and admin prompt motion
CI / Windows baseline (push) Has been cancelled
CI / Windows baseline (push) Has been cancelled
This commit is contained in:
+84
-22
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user