mirror of
https://github.com/fankes/beszel.git
synced 2025-10-19 01:39:34 +08:00
add dialog for copy to clipboard fallback (fixes #152)
This commit is contained in:
49
beszel/site/src/components/copy-to-clipboard.tsx
Normal file
49
beszel/site/src/components/copy-to-clipboard.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { useEffect, useMemo, useRef } from 'react'
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from './ui/dialog'
|
||||
import { Textarea } from './ui/textarea'
|
||||
import { $copyContent } from '@/lib/stores'
|
||||
|
||||
export default function CopyToClipboard({ content }: { content: string }) {
|
||||
return (
|
||||
<Dialog defaultOpen={true}>
|
||||
<DialogContent className="w-[90%] rounded-lg" style={{ maxWidth: 530 }}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Could not copy to clipboard</DialogTitle>
|
||||
<DialogDescription>Please copy the text manually.</DialogDescription>
|
||||
</DialogHeader>
|
||||
<CopyTextarea content={content} />
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Clipboard API requires a secure context (https, localhost, or *.localhost)
|
||||
</p>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
function CopyTextarea({ content }: { content: string }) {
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null)
|
||||
|
||||
const rows = useMemo(() => {
|
||||
return content.split('\n').length
|
||||
}, [content])
|
||||
|
||||
useEffect(() => {
|
||||
if (textareaRef.current) {
|
||||
textareaRef.current.select()
|
||||
}
|
||||
}, [textareaRef])
|
||||
|
||||
useEffect(() => {
|
||||
return () => $copyContent.set('')
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Textarea
|
||||
className="font-mono overflow-hidden whitespace-pre"
|
||||
rows={rows}
|
||||
value={content}
|
||||
readOnly
|
||||
ref={textareaRef}
|
||||
/>
|
||||
)
|
||||
}
|
23
beszel/site/src/components/ui/textarea.tsx
Normal file
23
beszel/site/src/components/ui/textarea.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||
|
||||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
'flex min-h-14 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Textarea.displayName = 'Textarea'
|
||||
|
||||
export { Textarea }
|
@@ -25,3 +25,6 @@ export const $chartTime = atom('1h') as WritableAtom<ChartTimes>
|
||||
|
||||
/** Container chart filter */
|
||||
export const $containerFilter = atom('')
|
||||
|
||||
/** Fallback copy to clipboard dialog content */
|
||||
export const $copyContent = atom('')
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { toast } from '@/components/ui/use-toast'
|
||||
import { type ClassValue, clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { $alerts, $systems, pb } from './stores'
|
||||
import { $alerts, $copyContent, $systems, pb } from './stores'
|
||||
import { AlertRecord, ChartTimeData, ChartTimes, SystemRecord } from '@/types'
|
||||
import { RecordModel, RecordSubscription } from 'pocketbase'
|
||||
import { WritableAtom } from 'nanostores'
|
||||
@@ -22,10 +22,7 @@ export async function copyToClipboard(content: string) {
|
||||
description: 'Copied to clipboard',
|
||||
})
|
||||
} catch (e: any) {
|
||||
prompt(
|
||||
'Automatic copy requires a secure context (https, localhost, or *.localhost). Please copy manually:',
|
||||
content
|
||||
)
|
||||
$copyContent.set(content)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,7 +3,14 @@ import React, { Suspense, lazy, useEffect } from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import Home from './components/routes/home.tsx'
|
||||
import { ThemeProvider } from './components/theme-provider.tsx'
|
||||
import { $authenticated, $systems, pb, $publicKey, $hubVersion } from './lib/stores.ts'
|
||||
import {
|
||||
$authenticated,
|
||||
$systems,
|
||||
pb,
|
||||
$publicKey,
|
||||
$hubVersion,
|
||||
$copyContent,
|
||||
} from './lib/stores.ts'
|
||||
import { ModeToggle } from './components/mode-toggle.tsx'
|
||||
import {
|
||||
cn,
|
||||
@@ -42,6 +49,7 @@ import { AddSystemButton } from './components/add-system.tsx'
|
||||
// const ServerDetail = lazy(() => import('./components/routes/system.tsx'))
|
||||
const CommandPalette = lazy(() => import('./components/command-palette.tsx'))
|
||||
const LoginPage = lazy(() => import('./components/login/login.tsx'))
|
||||
const CopyToClipboardDialog = lazy(() => import('./components/copy-to-clipboard.tsx'))
|
||||
|
||||
const App = () => {
|
||||
const page = useStore($router)
|
||||
@@ -96,6 +104,7 @@ const App = () => {
|
||||
|
||||
const Layout = () => {
|
||||
const authenticated = useStore($authenticated)
|
||||
const copyContent = useStore($copyContent)
|
||||
|
||||
if (!authenticated) {
|
||||
return (
|
||||
@@ -187,16 +196,23 @@ const Layout = () => {
|
||||
<Suspense>
|
||||
<CommandPalette />
|
||||
</Suspense>
|
||||
{copyContent && (
|
||||
<Suspense>
|
||||
<CopyToClipboardDialog content={copyContent} />
|
||||
</Suspense>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('app')!).render(
|
||||
<React.StrictMode>
|
||||
<ThemeProvider>
|
||||
<Layout />
|
||||
<Toaster />
|
||||
</ThemeProvider>
|
||||
</React.StrictMode>
|
||||
// strict mode in dev mounts / unmounts components twice
|
||||
// and breaks the clipboard dialog
|
||||
//<React.StrictMode>
|
||||
<ThemeProvider>
|
||||
<Layout />
|
||||
<Toaster />
|
||||
</ThemeProvider>
|
||||
//</React.StrictMode>
|
||||
)
|
||||
|
@@ -16,12 +16,12 @@ module.exports = {
|
||||
'2xl': '1400px',
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
sans: 'Inter, sans-serif',
|
||||
// body: ['Inter', 'sans-serif'],
|
||||
// display: ['Inter', 'sans-serif'],
|
||||
},
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: 'Inter, sans-serif',
|
||||
// body: ['Inter', 'sans-serif'],
|
||||
// display: ['Inter', 'sans-serif'],
|
||||
},
|
||||
screens: {
|
||||
xs: '425px',
|
||||
},
|
||||
|
Reference in New Issue
Block a user