mirror of
https://github.com/fromchat-messenger/web.git
synced 2026-09-22 19:15:08 +03:00
Redesign the download table, finish download dialog
This commit is contained in:
@@ -40,7 +40,6 @@ export function SplitButton({
|
||||
top?: number;
|
||||
bottom?: number;
|
||||
left: number;
|
||||
transform: string;
|
||||
maxHeight: number;
|
||||
} | null>(null);
|
||||
const { width: windowWidth, height: windowHeight } = useWindowSize();
|
||||
@@ -58,9 +57,8 @@ export function SplitButton({
|
||||
const menuWidth = menuEl?.offsetWidth ?? 220;
|
||||
const menuHeight = menuEl?.offsetHeight ?? 320;
|
||||
|
||||
const anchorX = rect.left + rect.width / 2;
|
||||
const anchorCenterX = rect.left + rect.width / 2;
|
||||
let left: number;
|
||||
let transform: string;
|
||||
let top: number | undefined;
|
||||
let bottom: number | undefined;
|
||||
let maxHeight: number;
|
||||
@@ -82,18 +80,15 @@ export function SplitButton({
|
||||
maxHeight = Math.max(100, availableBelow);
|
||||
}
|
||||
|
||||
if (anchorX - menuWidth / 2 < EDGE_PAD) {
|
||||
if (anchorCenterX - menuWidth / 2 < EDGE_PAD) {
|
||||
left = EDGE_PAD;
|
||||
transform = "none";
|
||||
} else if (anchorX + menuWidth / 2 > vw - EDGE_PAD) {
|
||||
} else if (anchorCenterX + menuWidth / 2 > vw - EDGE_PAD) {
|
||||
left = vw - menuWidth - EDGE_PAD;
|
||||
transform = "none";
|
||||
} else {
|
||||
left = anchorX;
|
||||
transform = "translateX(-50%)";
|
||||
left = anchorCenterX - menuWidth / 2;
|
||||
}
|
||||
|
||||
setMenuPosition({ top, bottom, left, transform, maxHeight });
|
||||
setMenuPosition({ top, bottom, left, maxHeight });
|
||||
}, []);
|
||||
|
||||
const closeMenu = useCallback(() => {
|
||||
@@ -245,7 +240,6 @@ export function SplitButton({
|
||||
? { bottom: menuPosition.bottom }
|
||||
: { top: menuPosition.top }),
|
||||
left: menuPosition.left,
|
||||
transform: menuPosition.transform,
|
||||
maxHeight: menuPosition.maxHeight,
|
||||
overflowY: "auto",
|
||||
}}
|
||||
|
||||
@@ -224,4 +224,8 @@ $menu-padding: 8px;
|
||||
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba($color-dark-on-surface, 0.25) transparent;
|
||||
|
||||
mdui-list {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
+4
-5
@@ -1,8 +1,8 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { StyledDialog } from "./StyledDialog";
|
||||
import { StyledDialog } from "@/core/components/StyledDialog";
|
||||
import { MaterialButton, MaterialIcon } from "@/utils/material";
|
||||
import { OS_CONFIG, type DownloadOs } from "@/core/downloads/os";
|
||||
import styles from "./css/download-dialog.module.scss";
|
||||
import { OS_CONFIG, type DownloadOs } from "@/pages/home/os";
|
||||
import styles from "@/pages/home/download-dialog.module.scss";
|
||||
|
||||
interface DownloadDialogProps {
|
||||
open: boolean;
|
||||
@@ -98,7 +98,7 @@ export function DownloadDialog({ open, onOpenChange, os }: DownloadDialogProps)
|
||||
<MaterialIcon name="download" className={styles.icon} />
|
||||
</div>
|
||||
<div className={styles.titleBlock}>
|
||||
<h2 className={styles.title}>Thanks for downloading</h2>
|
||||
<h2 className={styles.title}>Спасибо за скачивание!</h2>
|
||||
<p className={styles.subtitle}>
|
||||
FromChat для
|
||||
<span className={styles.osName}>{osInfo.label}</span>
|
||||
@@ -110,4 +110,3 @@ export function DownloadDialog({ open, onOpenChange, os }: DownloadDialogProps)
|
||||
</StyledDialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Link } from "react-router-dom";
|
||||
import { MaterialIcon } from "@/utils/material";
|
||||
import { GitHubLink, GITHUB_WEB, GITHUB_APP, GITHUB_LICENSE } from "./homeLinks";
|
||||
import styles from "./home-footer.module.scss";
|
||||
import { GitHubLink, GITHUB_WEB, GITHUB_APP, GITHUB_LICENSE } from "@/pages/home/homeLinks";
|
||||
import styles from "@/pages/home/home-footer.module.scss";
|
||||
|
||||
export function HomeFooter() {
|
||||
return (
|
||||
|
||||
@@ -2,8 +2,8 @@ import { useNavigate } from "react-router-dom";
|
||||
import { useUserStore } from "@/state/user";
|
||||
import useDownloadAppScreen from "@/core/hooks/useDownloadAppScreen";
|
||||
import { MaterialButton, MaterialIconButton } from "@/utils/material";
|
||||
import { GitHubLink, SupportLink } from "./homeLinks";
|
||||
import styles from "./home-header.module.scss";
|
||||
import { GitHubLink, SupportLink } from "@/pages/home/homeLinks";
|
||||
import styles from "@/pages/home/home-header.module.scss";
|
||||
|
||||
export function HomeHeader() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useState, type ReactNode } from "react";
|
||||
import styles from "./home.module.scss";
|
||||
import styles from "@/pages/home/home.module.scss";
|
||||
import useDownloadAppScreen from "@/core/hooks/useDownloadAppScreen";
|
||||
import { MaterialButton, MaterialIcon, MaterialList, MaterialListItem } from "@/utils/material";
|
||||
import generalChatScreenshot from "../../images/screenshots/general-chat.png";
|
||||
import dmScreenshot from "../../images/screenshots/dm.png";
|
||||
import windowsIcon from "../../images/windows.svg";
|
||||
import linuxIcon from "../../images/linux.svg";
|
||||
import macIcon from "../../images/mac.svg";
|
||||
import { HomeHeader } from "./HomeHeader";
|
||||
import { HomeFooter } from "./HomeFooter";
|
||||
import { MaterialButton, MaterialIcon, MaterialIconButton, MaterialList, MaterialListItem } from "@/utils/material";
|
||||
import generalChatScreenshot from "@/images/screenshots/general-chat.png";
|
||||
import dmScreenshot from "@/images/screenshots/dm.png";
|
||||
import windowsIcon from "@/images/windows.svg";
|
||||
import linuxIcon from "@/images/linux.svg";
|
||||
import macIcon from "@/images/mac.svg";
|
||||
import { HomeHeader } from "@/pages/home/HomeHeader";
|
||||
import { HomeFooter } from "@/pages/home/HomeFooter";
|
||||
import { SplitButton } from "@/core/components/SplitButton";
|
||||
import { DownloadDialog } from "@/core/components/DownloadDialog";
|
||||
import { OS_CONFIG, ALL_OS, detectOs, type DownloadOs } from "@/core/downloads/os";
|
||||
import { DownloadDialog } from "@/pages/home/DownloadDialog";
|
||||
import { OS_CONFIG, ALL_OS, detectOs, type DownloadOs } from "@/pages/home/os";
|
||||
|
||||
interface FeatureSectionProps {
|
||||
title: ReactNode;
|
||||
@@ -74,6 +74,14 @@ export default function HomePage() {
|
||||
return true;
|
||||
};
|
||||
|
||||
const getButtonVariant = (os: DownloadOs): "filled" | "tonal" | "outlined" => {
|
||||
const detectedOs = detectOs();
|
||||
if (os === detectedOs) return "filled";
|
||||
if (!isMobile && ["windows", "linux", "macos"].includes(os)) return "tonal";
|
||||
if (isMobile && (os === "android" || os === "ios") && os !== detectedOs) return "tonal";
|
||||
return "outlined";
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.homepage}>
|
||||
<HomeHeader />
|
||||
@@ -156,36 +164,63 @@ export default function HomePage() {
|
||||
Настольное приложение с уведомлениями и автономной работой
|
||||
или мобильное приложение для Android и iOS.
|
||||
</p>
|
||||
<div className={styles.downloadTable}>
|
||||
<div className={styles.downloadTableHeader}>
|
||||
<span>Платформа</span>
|
||||
<span>Описание</span>
|
||||
<span />
|
||||
</div>
|
||||
{ALL_OS.map((os) => (
|
||||
<div key={os} className={styles.downloadTableRow}>
|
||||
<div className={styles.downloadTableOs}>
|
||||
<MaterialIcon
|
||||
name={OS_CONFIG[os].icon}
|
||||
className={styles.downloadTableIcon}
|
||||
/>
|
||||
<span>{OS_CONFIG[os].label}</span>
|
||||
</div>
|
||||
<span className={styles.downloadTableDesc}>
|
||||
{OS_CONFIG[os].description}
|
||||
</span>
|
||||
<div className={styles.downloadTableAction}>
|
||||
<MaterialButton
|
||||
variant="outlined"
|
||||
icon="download"
|
||||
onClick={() => triggerDownload(os)}
|
||||
>
|
||||
Скачать
|
||||
</MaterialButton>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<table className={styles.downloadTable}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Платформа</th>
|
||||
<th>Описание</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{ALL_OS.map((os) => (
|
||||
<tr key={os}>
|
||||
<td className={styles.downloadTableOs}>
|
||||
<span className={styles.downloadTableOsContent}>
|
||||
{["windows", "linux", "macos"].includes(os) ? (
|
||||
<span
|
||||
className={styles.tableOsIcon}
|
||||
style={{
|
||||
"--table-os-icon-url": `url("${os === "windows" ? windowsIcon : os === "linux" ? linuxIcon : macIcon}")`,
|
||||
} as React.CSSProperties}
|
||||
/>
|
||||
) : (
|
||||
<MaterialIcon
|
||||
name={OS_CONFIG[os].icon}
|
||||
className={styles.downloadTableIcon}
|
||||
/>
|
||||
)}
|
||||
<span>{OS_CONFIG[os].label}</span>
|
||||
</span>
|
||||
</td>
|
||||
<td className={styles.downloadTableDesc}>
|
||||
<span className={styles.downloadTableDescInner}>
|
||||
{OS_CONFIG[os].description}
|
||||
</span>
|
||||
</td>
|
||||
<td className={styles.downloadTableAction}>
|
||||
<span className={styles.downloadTableActionInner}>
|
||||
<MaterialButton
|
||||
variant={getButtonVariant(os)}
|
||||
icon="download"
|
||||
className={styles.downloadButton}
|
||||
onClick={() => triggerDownload(os)}
|
||||
>
|
||||
Скачать
|
||||
</MaterialButton>
|
||||
<MaterialIconButton
|
||||
variant={getButtonVariant(os)}
|
||||
icon="download"
|
||||
className={styles.downloadButtonIcon}
|
||||
onClick={() => triggerDownload(os)}
|
||||
title={`Скачать ${OS_CONFIG[os].label}`}
|
||||
/>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use "../../css/material" as *;
|
||||
@use "@/css/material" as *;
|
||||
|
||||
$gradient-rainbow: linear-gradient(45deg, #9333EA, #6366F1, #3B82F6, #A855F7, #D946EF, #EC4899, #7E22CE);
|
||||
$glow-purple: rgba(147, 51, 234, 0.5);
|
||||
|
||||
+2
-3
@@ -1,5 +1,5 @@
|
||||
@use "../../../css/material" as *;
|
||||
@use "../../../css/colors" as *;
|
||||
@use "@/css/material" as *;
|
||||
@use "@/css/colors" as *;
|
||||
@use "sass:color";
|
||||
|
||||
.downloadDialog {
|
||||
@@ -111,4 +111,3 @@
|
||||
padding-inline: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use "../../css/material" as *;
|
||||
@use "@/css/material" as *;
|
||||
@use "home-shared" as shared;
|
||||
|
||||
.homepageFooter {
|
||||
@@ -37,7 +37,7 @@
|
||||
.footerLogo {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-image: url('../../images/logo_square.svg');
|
||||
background-image: url('@/images/logo_square.svg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
@@ -111,11 +111,11 @@
|
||||
}
|
||||
|
||||
.footerLinkIconSvgTelegram {
|
||||
mask-image: url('../../images/telegram.svg');
|
||||
-webkit-mask-image: url('../../images/telegram.svg');
|
||||
mask-image: url('@/images/telegram.svg');
|
||||
-webkit-mask-image: url('@/images/telegram.svg');
|
||||
}
|
||||
|
||||
.footerLinkIconSvgMax {
|
||||
mask-image: url('../../images/max.svg');
|
||||
-webkit-mask-image: url('../../images/max.svg');
|
||||
mask-image: url('@/images/max.svg');
|
||||
-webkit-mask-image: url('@/images/max.svg');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use "../../css/material" as *;
|
||||
@use "@/css/material" as *;
|
||||
@use "home-shared" as shared;
|
||||
|
||||
.homepageHeader {
|
||||
@@ -35,7 +35,7 @@
|
||||
.logoIcon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-image: url('../../images/logo_square.svg');
|
||||
background-image: url('@/images/logo_square.svg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
@use "../../css/material" as *;
|
||||
@use "@/css/material" as *;
|
||||
@use "home-shared" as shared;
|
||||
|
||||
// Shared variables (home-specific)
|
||||
@@ -15,7 +15,18 @@ $gradient-conic: conic-gradient(
|
||||
rgba(147, 51, 234, 0.5) 100%
|
||||
);
|
||||
$radius-card: 20px;
|
||||
$radius-pill: 9999px;
|
||||
$radius-pill: 25px;
|
||||
|
||||
// Download table variables
|
||||
$download-cell-padding: 14px 20px;
|
||||
$download-cell-gap: 12px;
|
||||
$download-icon-size: 24px;
|
||||
$download-col1-w: 35%;
|
||||
$download-col2-w: 30%;
|
||||
$download-col3-w: 35%;
|
||||
$download-row-bg-hover: rgba($color-dark-surface-container-high, 0.6);
|
||||
$download-header-bg: rgba($color-dark-surface-container-high, 0.4);
|
||||
$download-text-color: $color-dark-on-surface-variant;
|
||||
|
||||
// Mixins
|
||||
@mixin section-heading {
|
||||
@@ -44,6 +55,30 @@ $radius-pill: 9999px;
|
||||
padding: 0 32px;
|
||||
}
|
||||
|
||||
@mixin download-cell-inner($justify) {
|
||||
display: flex;
|
||||
justify-content: $justify;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@mixin masked-icon($url-var) {
|
||||
display: inline-block;
|
||||
width: $download-icon-size;
|
||||
height: $download-icon-size;
|
||||
flex-shrink: 0;
|
||||
background-color: $download-text-color;
|
||||
mask-image: var(#{$url-var});
|
||||
-webkit-mask-image: var(#{$url-var});
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
-webkit-mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
-webkit-mask-position: center;
|
||||
}
|
||||
|
||||
.homepage {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
@@ -96,7 +131,7 @@ $radius-pill: 9999px;
|
||||
.titleLogo {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
background-image: url('../../images/logo_square.svg');
|
||||
background-image: url('@/images/logo_square.svg');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
@@ -212,66 +247,169 @@ $radius-pill: 9999px;
|
||||
}
|
||||
|
||||
.downloadTable {
|
||||
display: table;
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
border-radius: 20px;
|
||||
background: rgba($color-dark-surface-container, 0.8);
|
||||
border: 1px solid rgba($color-dark-outline, 0.3);
|
||||
padding: 8px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
border-radius: 16px;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0 6px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
table-layout: auto;
|
||||
|
||||
.downloadTableHeader {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 2fr auto;
|
||||
padding: 8px 4px;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: $color-dark-on-surface-variant;
|
||||
}
|
||||
th:first-child,
|
||||
td:first-child {
|
||||
width: 1%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.downloadTableRow {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 2fr auto;
|
||||
align-items: center;
|
||||
padding: 8px 4px;
|
||||
border-radius: 12px;
|
||||
&:hover {
|
||||
background-color: rgba($color-dark-surface-container-high, 0.8);
|
||||
th:nth-child(2),
|
||||
td:nth-child(2) {
|
||||
width: 1%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
th:last-child,
|
||||
td:last-child {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
thead {
|
||||
display: table-header-group;
|
||||
|
||||
tr th {
|
||||
padding: $download-cell-padding;
|
||||
display: table-cell;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: $download-text-color;
|
||||
background: $download-header-bg;
|
||||
vertical-align: middle;
|
||||
transition: background-color 0.2s ease;
|
||||
text-align: left;
|
||||
|
||||
&:first-child {
|
||||
border-radius: 12px 0 0 12px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 12px 12px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
display: table-row-group;
|
||||
|
||||
tr {
|
||||
display: table-row;
|
||||
background: $color-dark-surface-container-low;
|
||||
|
||||
td {
|
||||
padding: $download-cell-padding;
|
||||
display: table-cell;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
transition: background-color 0.2s ease;
|
||||
background: transparent;
|
||||
|
||||
&:first-child {
|
||||
text-align: left;
|
||||
border-radius: $radius-pill 0 0 $radius-pill;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
text-align: right;
|
||||
border-radius: 0 $radius-pill $radius-pill 0;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover td {
|
||||
background: $download-row-bg-hover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.downloadTableOs {
|
||||
text-align: left;
|
||||
|
||||
.downloadTableOsContent {
|
||||
@include download-cell-inner(flex-start);
|
||||
gap: $download-cell-gap;
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.downloadTableIcon {
|
||||
font-size: $download-icon-size;
|
||||
width: $download-icon-size;
|
||||
height: $download-icon-size;
|
||||
color: $download-text-color;
|
||||
}
|
||||
|
||||
.tableOsIcon {
|
||||
@include masked-icon("--table-os-icon-url");
|
||||
}
|
||||
|
||||
.downloadTableDesc {
|
||||
font-size: 14px;
|
||||
color: $download-text-color;
|
||||
|
||||
.downloadTableDescInner {
|
||||
@include download-cell-inner(flex-start);
|
||||
}
|
||||
}
|
||||
|
||||
.downloadTableAction {
|
||||
text-align: right;
|
||||
|
||||
.downloadTableActionInner {
|
||||
@include download-cell-inner(flex-end);
|
||||
}
|
||||
}
|
||||
|
||||
.downloadButton {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.downloadButtonIcon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.downloadTableOs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
@media (max-width: 490px) {
|
||||
.downloadContent .downloadTable {
|
||||
.downloadButton {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.downloadButtonIcon {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.downloadTableIcon {
|
||||
font-size: 20px;
|
||||
color: $color-dark-primary;
|
||||
opacity: 0.9;
|
||||
@media (max-width: 405px) {
|
||||
.downloadContent .downloadTable {
|
||||
th:nth-child(2),
|
||||
td:nth-child(2) {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.downloadButton {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.downloadButtonIcon {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.downloadTableDesc {
|
||||
font-size: 14px;
|
||||
color: $color-dark-on-surface-variant;
|
||||
}
|
||||
|
||||
.downloadTableAction {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.downloadTableActionLink {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,18 +479,7 @@ $radius-pill: 9999px;
|
||||
}
|
||||
|
||||
.menuCustomIcon {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background-color: $color-dark-on-surface-variant;
|
||||
mask-image: var(--menu-custom-icon-url);
|
||||
-webkit-mask-image: var(--menu-custom-icon-url);
|
||||
mask-size: contain;
|
||||
mask-repeat: no-repeat;
|
||||
mask-position: center;
|
||||
-webkit-mask-size: contain;
|
||||
-webkit-mask-repeat: no-repeat;
|
||||
-webkit-mask-position: center;
|
||||
@include masked-icon("--menu-custom-icon-url");
|
||||
}
|
||||
|
||||
// Keyframes for animations
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
export type DownloadOs = "windows" | "linux" | "macos" | "android" | "ios";
|
||||
|
||||
export const ALL_OS: DownloadOs[] = [
|
||||
"windows",
|
||||
"linux",
|
||||
"macos",
|
||||
"android",
|
||||
"ios",
|
||||
] as const;
|
||||
|
||||
export interface OsInfo {
|
||||
id: DownloadOs;
|
||||
label: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export const OS_CONFIG: Record<DownloadOs, OsInfo> = {
|
||||
windows: { id: "windows", label: "Windows", description: "ПК", icon: "computer" },
|
||||
linux: { id: "linux", label: "Linux", description: "ПК", icon: "computer" },
|
||||
macos: { id: "macos", label: "macOS", description: "Apple", icon: "computer" },
|
||||
android: { id: "android", label: "Android", description: "APK", icon: "android" },
|
||||
ios: { id: "ios", label: "iOS", description: "iPhone, iPad", icon: "phone_iphone" },
|
||||
};
|
||||
|
||||
export function detectOs(): DownloadOs {
|
||||
if (typeof navigator === "undefined") {
|
||||
return "android";
|
||||
}
|
||||
|
||||
const ua = (navigator.userAgent || navigator.platform || "").toLowerCase();
|
||||
const platform = (navigator as any).userAgentData?.platform?.toLowerCase?.() ?? "";
|
||||
const haystack = `${ua} ${platform}`;
|
||||
|
||||
if (haystack.includes("android")) {
|
||||
return "android";
|
||||
}
|
||||
|
||||
if (haystack.includes("iphone") || haystack.includes("ipad") || haystack.includes("ipod")) {
|
||||
return "ios";
|
||||
}
|
||||
|
||||
if (haystack.includes("win")) {
|
||||
return "windows";
|
||||
}
|
||||
|
||||
if (haystack.includes("mac")) {
|
||||
return "macos";
|
||||
}
|
||||
|
||||
if (haystack.includes("linux")) {
|
||||
return "linux";
|
||||
}
|
||||
|
||||
return "android";
|
||||
}
|
||||
Reference in New Issue
Block a user