layout: add window borders on Linux, closes #1570 (#1599)

This commit is contained in:
Huang Xin
2025-07-18 01:40:12 +08:00
committed by GitHub
parent 6da6b85530
commit ee0d1549bd
11 changed files with 96 additions and 58 deletions
+4 -2
View File
@@ -334,8 +334,10 @@ export default function AuthPage() {
return isTauriAppPlatform() ? (
<div
className={clsx(
'fixed inset-0 z-0 flex select-none flex-col items-center overflow-y-auto',
'bg-base-100 border-base-200 border',
'bg-base-100 fixed inset-0 flex select-none flex-col items-center overflow-hidden overflow-y-auto',
appService?.isIOSApp ? 'h-[100vh]' : 'h-dvh',
appService?.isLinuxApp && 'window-border',
appService?.hasRoundedWindow && 'rounded-window',
appService?.hasSafeAreaInset && 'pt-[env(safe-area-inset-top)]',
)}
>
@@ -669,6 +669,7 @@ const LibraryPageContent = ({ searchParams }: { searchParams: ReadonlyURLSearchP
className={clsx(
'library-page bg-base-200 text-base-content flex select-none flex-col overflow-hidden',
appService?.isIOSApp ? 'h-[100vh]' : 'h-dvh',
appService?.isLinuxApp && 'window-border',
appService?.hasRoundedWindow && 'rounded-window',
)}
>
@@ -31,7 +31,7 @@ const BooksGrid: React.FC<BooksGridProps> = ({ bookKeys, onCloseBook }) => {
const { getConfig, getBookData } = useBookDataStore();
const { getProgress, getViewState, getViewSettings } = useReaderStore();
const { setGridInsets, hoveredBookKey } = useReaderStore();
const { isSideBarVisible, sideBarBookKey } = useSidebarStore();
const { sideBarBookKey } = useSidebarStore();
const { isFontLayoutSettingsDialogOpen, setFontLayoutSettingsDialogOpen } = useSettingsStore();
const screenInsets = useSafeAreaInsets();
@@ -106,7 +106,7 @@ const BooksGrid: React.FC<BooksGridProps> = ({ bookKeys, onCloseBook }) => {
key={bookKey}
className={clsx(
'relative h-full w-full overflow-hidden',
!isSideBarVisible && appService?.hasRoundedWindow && 'rounded-window',
appService?.hasRoundedWindow && 'rounded-window',
)}
>
{isBookmarked && !hoveredBookKey && <Ribbon width={`${horizontalGapPercent}%`} />}
@@ -92,6 +92,7 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
<div
className={clsx(
'bg-base-100 absolute left-0 right-0 top-0 z-10',
appService?.hasRoundedWindow && 'rounded-window-top-right',
isVisible ? 'visible' : 'hidden',
)}
style={{
@@ -111,8 +111,9 @@ const Reader: React.FC<{ ids?: string }> = ({ ids }) => {
settings.globalReadSettings && (
<div
className={clsx(
`reader-page bg-base-100 text-base-content select-none`,
!isSideBarVisible && appService?.hasRoundedWindow && 'rounded-window',
`reader-page bg-base-100 text-base-content select-none overflow-hidden`,
appService?.isLinuxApp && 'window-border',
appService?.hasRoundedWindow && 'rounded-window',
)}
>
<Suspense>
@@ -4,6 +4,7 @@ import { MdBookmarkBorder as BookmarkIcon } from 'react-icons/md';
import { IoIosList as TOCIcon } from 'react-icons/io';
import { PiNotePencil as NoteIcon } from 'react-icons/pi';
import { useEnv } from '@/context/EnvContext';
import { useTranslation } from '@/hooks/useTranslation';
const TabNavigation: React.FC<{
@@ -11,12 +12,16 @@ const TabNavigation: React.FC<{
onTabChange: (tab: string) => void;
}> = ({ activeTab, onTabChange }) => {
const _ = useTranslation();
const { appService } = useEnv();
const tabs = ['toc', 'annotations', 'bookmarks'];
return (
<div
className={clsx('bottom-tab border-base-300/50 bg-base-200 relative flex w-full border-t')}
className={clsx(
'bottom-tab border-base-300/50 bg-base-200 relative flex w-full border-t',
appService?.hasRoundedWindow && 'rounded-window-bottom-left',
)}
dir='ltr'
>
<div
+52 -48
View File
@@ -69,7 +69,7 @@ const ProfilePage = () => {
useTheme({ systemUIVisible: false });
const { quotas, userPlan } = useQuotaStats();
const { quotas, userPlan = 'free' } = useQuotaStats();
const handleGoBack = () => {
if (showEmbeddedCheckout) {
@@ -219,62 +219,66 @@ const ProfilePage = () => {
return (
<div
className={clsx(
'fixed inset-0 z-0 flex select-none flex-col items-center overflow-y-auto',
'bg-base-100 border-base-200 border',
'bg-base-100 fixed inset-0 select-none overflow-hidden',
appService?.isIOSApp ? 'h-[100vh]' : 'h-dvh',
appService?.isLinuxApp && 'window-border',
appService?.hasRoundedWindow && 'rounded-window',
appService?.hasSafeAreaInset && 'pt-[env(safe-area-inset-top)]',
)}
>
<ProfileHeader onGoBack={handleGoBack} />
<div className='w-full min-w-60 max-w-4xl py-10'>
{loading && (
<div className='fixed inset-0 z-50 flex items-center justify-center'>
<Spinner loading />
</div>
)}
{showEmbeddedCheckout ? (
<div className='bg-base-100 rounded-lg p-4'>
<Checkout
clientSecret={checkoutState.clientSecret}
sessionId={checkoutState.sessionId}
planName={checkoutState.planName}
onSuccess={handleCheckoutSuccess}
/>
</div>
) : (
<div className='sm:bg-base-200 overflow-hidden rounded-lg sm:p-6 sm:shadow-md'>
<div className='flex flex-col gap-y-8'>
<div className='flex flex-col gap-y-8 px-6'>
<UserInfo
avatarUrl={avatarUrl}
userFullName={userFullName}
userEmail={userEmail}
planDetails={planDetails}
/>
<div className='flex h-full w-full flex-col items-center overflow-y-auto'>
<ProfileHeader onGoBack={handleGoBack} />
<div className='w-full min-w-60 max-w-4xl py-10'>
{loading && (
<div className='fixed inset-0 z-50 flex items-center justify-center'>
<Spinner loading />
</div>
)}
{showEmbeddedCheckout ? (
<div className='bg-base-100 rounded-lg p-4'>
<Checkout
clientSecret={checkoutState.clientSecret}
sessionId={checkoutState.sessionId}
planName={checkoutState.planName}
onSuccess={handleCheckoutSuccess}
/>
</div>
) : (
<div className='sm:bg-base-200 overflow-hidden rounded-lg sm:p-6 sm:shadow-md'>
<div className='flex flex-col gap-y-8'>
<div className='flex flex-col gap-y-8 px-6'>
<UserInfo
avatarUrl={avatarUrl}
userFullName={userFullName}
userEmail={userEmail}
planDetails={planDetails}
/>
<UsageStats quotas={quotas} />
</div>
<UsageStats quotas={quotas} />
</div>
<div className='flex flex-col gap-y-8 sm:px-6'>
<PlansComparison
availablePlans={availablePlans}
userPlan={userPlan}
onSubscribe={handleSubscribe}
/>
</div>
<div className='flex flex-col gap-y-8 sm:px-6'>
<PlansComparison
availablePlans={availablePlans}
userPlan={userPlan}
onSubscribe={handleSubscribe}
/>
</div>
<div className='flex flex-col gap-y-8 px-6'>
<AccountActions
userPlan={userPlan}
onLogout={handleLogout}
onConfirmDelete={handleConfirmDelete}
onManageSubscription={handleManageSubscription}
/>
<div className='flex flex-col gap-y-8 px-6'>
<AccountActions
userPlan={userPlan}
onLogout={handleLogout}
onConfirmDelete={handleConfirmDelete}
onManageSubscription={handleManageSubscription}
/>
</div>
</div>
</div>
</div>
)}
)}
</div>
<Toast />
</div>
<Toast />
</div>
);
};
+5 -1
View File
@@ -160,7 +160,11 @@ const Dialog: React.FC<DialogProps> = ({
dir={isRtl ? 'rtl' : undefined}
>
<div
className={clsx('overlay fixed inset-0 z-10 bg-black/50 sm:bg-black/20', bgClassName)}
className={clsx(
'overlay fixed inset-0 z-10 bg-black/50 sm:bg-black/20',
appService?.hasRoundedWindow && 'rounded-window',
bgClassName,
)}
onClick={onClose}
/>
<div
+1 -1
View File
@@ -8,7 +8,7 @@ export const useQuotaStats = () => {
const _ = useTranslation();
const { token, user } = useAuth();
const [quotas, setQuotas] = useState<QuotaType[]>([]);
const [userPlan, setUserPlan] = useState<UserPlan>('free');
const [userPlan, setUserPlan] = useState<UserPlan | undefined>(undefined);
useEffect(() => {
if (!user || !token) return;
@@ -220,7 +220,7 @@ export class NativeAppService extends BaseAppService {
override hasWindow = !(OS_TYPE === 'ios' || OS_TYPE === 'android');
override hasWindowBar = !(OS_TYPE === 'ios' || OS_TYPE === 'android');
override hasContextMenu = !(OS_TYPE === 'ios' || OS_TYPE === 'android');
override hasRoundedWindow = !(OS_TYPE === 'ios' || OS_TYPE === 'android') && !!window.IS_ROUNDED;
override hasRoundedWindow = OS_TYPE === 'linux';
override hasSafeAreaInset = OS_TYPE === 'ios' || OS_TYPE === 'android';
override hasHaptics = OS_TYPE === 'ios' || OS_TYPE === 'android';
override hasUpdater =
+20
View File
@@ -6,6 +6,7 @@
border-radius: 10px;
scrollbar-gutter: auto !important;
overscroll-behavior: none;
background-color: transparent;
--safe-area-inset-top: env(safe-area-inset-top);
--safe-area-inset-right: env(safe-area-inset-right);
@@ -97,6 +98,25 @@ foliate-view {
border-bottom-right-radius: 10px;
}
.window-border {
border-radius: 10px;
z-index: 999;
position: relative;
}
.window-border::before {
content: '';
z-index: 1000;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border: 1px solid theme('colors.base-300');
border-radius: 10px;
pointer-events: none;
}
.dropdown-content {
position: relative;
@apply bg-base-100;